How to create a bottom aligned label in Swift? - ios

There are several examples on how to create a Top Aligned label in Swift. Here is on that works for me:
#IBDesignable class TopAlignedLabel: UILabel {
override func drawTextInRect(rect: CGRect) {
if let stringText = text {
let stringTextAsNSString = stringText as NSString
var labelStringSize = stringTextAsNSString.boundingRectWithSize(CGSizeMake(CGRectGetWidth(self.frame), CGFloat.max),
options: NSStringDrawingOptions.UsesLineFragmentOrigin,
attributes: [NSFontAttributeName: font],
context: nil).size
super.drawTextInRect(CGRectMake(0, 0, CGRectGetWidth(self.frame), ceil(labelStringSize.height)))
} else {
super.drawTextInRect(rect)
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer.borderWidth = 1
layer.borderColor = UIColor.blackColor().CGColor
}
}
I am trying to modify this to create a bottom aligned label, but struggling to find the right thing to change. Any help is appreciated!

Change this line:
super.drawTextInRect(CGRectMake(0, 0, CGRectGetWidth(self.frame), ceil(labelStringSize.height)))
Into this:
super.drawTextInRect(CGRectMake(0, rect.size.height - labelStringSize.height, CGRectGetWidth(self.frame), ceil(labelStringSize.height)))

Updating the solution for SWIFT 3.0, complete code becomes:
import UIKit
#IBDesignable class BottomAlignedLabel: UILabel {
override func drawText(in rect: CGRect) {
if let stringText = text {
let stringTextAsNSString = stringText as NSString
let labelStringSize = stringTextAsNSString.boundingRect(with: CGSize(width: self.frame.width,height: CGFloat.greatestFiniteMagnitude),
options: NSStringDrawingOptions.usesLineFragmentOrigin,
attributes: [NSFontAttributeName: font],
context: nil).size
super.drawText(in: CGRect(x:0,y: rect.size.height - labelStringSize.height, width: self.frame.width, height: ceil(labelStringSize.height)))
} else {
super.drawText(in: rect)
}
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
layer.borderWidth = 1
layer.borderColor = UIColor.clear.cgColor
}
}

Related

Detect clicked mutable attributed string

I have this code to write a paragraph like book with numbering for each sentence , the problem I'm facing is i can't find how to color one sentence when the user clicks in any word from it
import UIKit
let descender: CGFloat = UIFont.systemFont(ofSize: 25).descender
class ViewController: UIViewController , UITextViewDelegate, UIGestureRecognizerDelegate {
var all = [NSMutableAttributedString]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let style = NSMutableParagraphStyle()
style.alignment = NSTextAlignment.justified
style.baseWritingDirection = .rightToLeft
style.lineBreakMode = .byWordWrapping
let myAttribute = [ NSAttributedString.Key.font: UIFont.systemFont(ofSize: 25)] // ,
// NSAttributedString.Key.paragraphStyle: style ,
// NSAttributedString.Key.baselineOffset: NSNumber(value: 0)]
let textView = UITextView(frame:CGRect(x: 20, y: 100, width: UIScreen.main.bounds.width - 40 , height: UIScreen.main.bounds.height))
let attributedString = NSMutableAttributedString()
Array(1..<50).forEach {
let small = $0 % 2 == 0 ? " long text part one long text part one long text part one long text part one long text part one long text part one long text part one long text part one long text part one " : "long text part two long text part twolong text part twolong text part twolong text part twolong text part twolong text part twolong text part two "
let attributedString2 = NSMutableAttributedString(string: small,attributes: myAttribute)
attributedString.append(attributedString2)
let textAttachment11 = SubTextAttachment()
textAttachment11.image = generateImageWithText(text: "\($0)")
let attrStringWithImage11 = NSAttributedString(attachment: textAttachment11)
attributedString.append(attrStringWithImage11)
}
textView.attributedText = attributedString;
self.view.addSubview(textView)
textView.isEditable = false
textView.isSelectable = true
textView.delegate = self
let tap = UITapGestureRecognizer(target: self, action: #selector(self.textTapped(_:)))
tap.delegate = self
textView.isUserInteractionEnabled = true
textView.addGestureRecognizer(tap)
}
func generateImageWithText(text: String) -> UIImage? {
let image = UIImage(named: "qqq")!
print(text," ",image.size)
let imageView = UIImageView(image: image)
imageView.backgroundColor = UIColor.clear
imageView.frame = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
let label = UILabel(frame: CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height))
label.font = UIFont.systemFont(ofSize: 50)
label.backgroundColor = UIColor.clear
label.textAlignment = .center
label.textColor = UIColor.black
label.text = text
UIGraphicsBeginImageContextWithOptions(label.bounds.size, false, 0)
imageView.layer.render(in: UIGraphicsGetCurrentContext()!)
label.layer.render(in: UIGraphicsGetCurrentContext()!)
let imageWithText = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return imageWithText
}
#objc func textTapped(_ sender:UITapGestureRecognizer) {
}
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
return true
}
}
class SubTextAttachment:NSTextAttachment {
override func attachmentBounds(for textContainer: NSTextContainer?, proposedLineFragment lineFrag: CGRect, glyphPosition position: CGPoint, characterIndex charIndex: Int) -> CGRect {
let height = lineFrag.size.height
var scale: CGFloat = 1.0;
let imageSize = image!.size
if (height < imageSize.height) {
scale = height / imageSize.height
}
let value = CGRect(x: 0, y: descender, width: imageSize.width * scale, height: imageSize.height * scale)
return value
}
}
I know how to change the foreground color of any sub attributed string , but how i can know that the clicked part belong to the one to be colored ?
Also is there any better way to build this UI (in terms of performance ) as with tableView/CollectionView there is a dequeueing but here there isn't ?
So any hep is greatly appreciated
With NSAttributedString , you can use CoreText to render.
Convert NSAttributedString to CTFrame, then render it.
The key part
when you click a word in paragraph,
with override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)
you can get a CGPoint
with that CGPoint & CTFrame, you can know the text range clicked in the text.
then rebuild the NSAttributedString 、CTFrame & rerender
here is the code you can refer
import UIKit
import CoreText
class TextRenderView: UIView {
let frameRef:CTFrame
let theSize: CGSize
let keyOne = //...
let keyTwo = //...
let rawTxt: String
let contentPage: NSAttributedString
let keyRanges: [Range<String.Index>]
override init(frame: CGRect){
rawTxt = //...
var tempRanges = [Range<String.Index>]()
if let rangeOne = rawTxt.range(of: keyOne){
tempRanges.append(rangeOne)
}
if let rangeTwo = rawTxt.range(of: keyTwo){
tempRanges.append(rangeTwo)
}
keyRanges = tempRanges
contentPage = NSAttributedString(string: rawTxt, attributes: [NSAttributedString.Key.font: UIFont.regular(ofSize: 15), NSAttributedString.Key.foregroundColor: UIColor.black])
let calculatedSize = contentPage.boundingRect(with: CGSize(width: UI.std.width - CGFloat(15 * 2), height: UI.std.height), options: [.usesFontLeading, .usesLineFragmentOrigin], context: nil).size
let padding: CGFloat = 10
theSize = CGSize(width: calculatedSize.width, height: calculatedSize.height + padding)
let framesetter = CTFramesetterCreateWithAttributedString(contentPage)
let path = CGPath(rect: CGRect(origin: CGPoint.zero, size: theSize), transform: nil)
frameRef = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, 0), path, nil)
super.init(frame: frame)
backgroundColor = UIColor.white
}
required init?(coder: NSCoder) {
fatalError()
}
override func draw(_ rect: CGRect) {
guard let ctx = UIGraphicsGetCurrentContext() else{
return
}
ctx.textMatrix = CGAffineTransform.identity
ctx.translateBy(x: 0, y: bounds.size.height)
ctx.scaleBy(x: 1.0, y: -1.0)
CTFrameDraw(frameRef, ctx)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
super.touchesBegan(touches, with: event)
guard let touch = touches.first else{
return
}
let pt = touch.location(in: self)
guard let offset = parserRect(with: pt, frame: frameRef), let pos = rawTxt.index(rawTxt.startIndex, offsetBy: offset, limitedBy: rawTxt.endIndex) else{
return
}
if keyRanges[0].contains(pos){
print(0)
}
else if keyRanges[1].contains(pos){
print(1)
}
}
func parserRect(with point: CGPoint, frame textFrame: CTFrame) -> Int?{
var result: Int? = nil
let path: CGPath = CTFrameGetPath(textFrame)
let bounds = path.boundingBox
guard let lines = CTFrameGetLines(textFrame) as? [CTLine] else{
return result
}
let lineCount = lines.count
guard lineCount > 0 else {
return result
}
var origins = [CGPoint](repeating: CGPoint.zero, count: lineCount)
CTFrameGetLineOrigins(frameRef, CFRangeMake(0, 0), &origins)
for i in 0..<lineCount{
let baselineOrigin = origins[i]
let line = lines[i]
var ascent: CGFloat = 0
var descent: CGFloat = 0
var linegap: CGFloat = 0
let lineWidth = CTLineGetTypographicBounds(line, &ascent, &descent, &linegap)
let lineFrame = CGRect(x: baselineOrigin.x, y: bounds.height-baselineOrigin.y-ascent, width: CGFloat(lineWidth), height: ascent+descent+linegap + 10)
if lineFrame.contains(point){
result = CTLineGetStringIndexForPosition(line, point)
break
}
}
return result
}
}
helper method:
extension String {
func range(ns inner: String) -> NSRange{
return (self as NSString).range(of: inner)
}
}
here is the github code you can refer

iOS UISLider with gradient layer

I'm building an iOS application in which I have to implement a custom UISlider. The problem is that the built-in UISlider doesn't support gradient track. Another issue is my UI style guide shows that the current tracking value rectangle should be a gradient of two colors as shown in the image
How can I build a customized version of the UISlider? I have thought of either subclassing the existing one or by building a UIControl subclass.
I'm using xcode 9.4 and swift 4.2
Thanks in advance!
I ended up solving the problem by setting the gradient layer as an image for the slider minimum track image as following:
#IBDesignable
class GradientSlider: UISlider {
#IBInspectable var thickness: CGFloat = 20 {
didSet {
setup()
}
}
#IBInspectable var sliderThumbImage: UIImage? {
didSet {
setup()
}
}
func setup() {
let minTrackStartColor = Palette.SelectiveYellow
let minTrackEndColor = Palette.BitterLemon
let maxTrackColor = Palette.Firefly
do {
self.setMinimumTrackImage(try self.gradientImage(
size: self.trackRect(forBounds: self.bounds).size,
colorSet: [minTrackStartColor.cgColor, minTrackEndColor.cgColor]),
for: .normal)
self.setMaximumTrackImage(try self.gradientImage(
size: self.trackRect(forBounds: self.bounds).size,
colorSet: [maxTrackColor.cgColor, maxTrackColor.cgColor]),
for: .normal)
self.setThumbImage(sliderThumbImage, for: .normal)
} catch {
self.minimumTrackTintColor = minTrackStartColor
self.maximumTrackTintColor = maxTrackColor
}
}
func gradientImage(size: CGSize, colorSet: [CGColor]) throws -> UIImage? {
let tgl = CAGradientLayer()
tgl.frame = CGRect.init(x:0, y:0, width:size.width, height: size.height)
tgl.cornerRadius = tgl.frame.height / 2
tgl.masksToBounds = false
tgl.colors = colorSet
tgl.startPoint = CGPoint.init(x:0.0, y:0.5)
tgl.endPoint = CGPoint.init(x:1.0, y:0.5)
UIGraphicsBeginImageContextWithOptions(size, tgl.isOpaque, 0.0);
guard let context = UIGraphicsGetCurrentContext() else { return nil }
tgl.render(in: context)
let image =
UIGraphicsGetImageFromCurrentImageContext()?.resizableImage(withCapInsets:
UIEdgeInsets.init(top: 0, left: size.height, bottom: 0, right: size.height))
UIGraphicsEndImageContext()
return image!
}
override func trackRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(
x: bounds.origin.x,
y: bounds.origin.y,
width: bounds.width,
height: thickness
)
}
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
}

How to marquee UITextField placeholder if text longer then the UITextField width?

Is it possible to marquee UITextFiled placeholder, if placeholder text is longer then the size of UITextField width, There are lib for UILabel MarqueeLabel but I am not sure how to marquee placeholder, please provide some suggestion, Or you can explain what is a placeholder is actually, it doesn't look like UILabel
I am using bellow code for CustomTextField with validation error message
import UIKit
#IBDesignable
class CustomTextField: UITextField {
var placeholdertext: String?
#IBInspectable
public var cornerRadius :CGFloat {
set { layer.cornerRadius = newValue }
get {
return layer.cornerRadius
}
}
// Provides left padding for images
override func leftViewRect(forBounds bounds: CGRect) -> CGRect {
var textRect = super.leftViewRect(forBounds: bounds)
textRect.origin.x += leftPadding
return textRect
}
override func rightViewRect(forBounds bounds: CGRect) -> CGRect {
var textRect = super.rightViewRect(forBounds: bounds)
textRect.origin.x -= rightPadding
return textRect
}
#IBInspectable var leftImage: UIImage? {
didSet {
updateView()
}
}
#IBInspectable var isUnderLine:Bool = false
{
didSet{
updateView()
}
}
#IBInspectable var rightImage: UIImage? {
didSet {
updateView()
}
}
func setError(error:String?){
if(error != nil)
{
self.attributedPlaceholder = NSAttributedString(string: error!, attributes: [NSForegroundColorAttributeName: UIColor.red])
}
}
#IBInspectable var leftPadding: CGFloat = 0
#IBInspectable var rightPadding: CGFloat = 0
#IBInspectable var textLeftPadding:CGFloat = 0
#IBInspectable var color: UIColor = UIColor.lightGray {
didSet {
updateView()
}
}
#IBInspectable var underlineColor:UIColor = UIColor.black
{
didSet{
self.updateView()
}
}
private var placeholderColorValue:UIColor = UIColor.lightGray
#IBInspectable public var placeholderColor:UIColor
{
set{
self.attributedPlaceholder = NSAttributedString(string:placeholder!, attributes: [NSForegroundColorAttributeName: newValue])
placeholderColorValue = newValue
}
get{
return placeholderColorValue
}
}
override init(frame: CGRect) {
super.init(frame: frame)
self.setUpView()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func awakeFromNib() {
super.awakeFromNib()
self.setUpView()
}
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
}
func setUpView() {
if(rightImage != nil)
{
self.leftViewMode = UITextFieldViewMode.always
let rightImageView:UIImageView = UIImageView(image: rightImage)
rightImageView.frame = CGRect(x: 0, y: 0, width: self.frame.size.height, height: self.frame.size.height)
self.rightView = rightImageView
}
else {
rightViewMode = UITextFieldViewMode.never
rightView = nil
}
}
func updateView() {
if let imageLeft = leftImage {
leftViewMode = UITextFieldViewMode.always
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
imageView.image = imageLeft
// Note: In order for your image to use the tint color, you have to select the image in the Assets.xcassets and change the "Render As" property to "Template Image".
imageView.tintColor = color
leftView = imageView
} else {
leftViewMode = UITextFieldViewMode.never
leftView = nil
}
if let imageRight = rightImage {
rightViewMode = UITextFieldViewMode.always
let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
imageView.image = imageRight
// Note: In order for your image to use the tint color, you have to select the image in the Assets.xcassets and change the "Render As" property to "Template Image".
imageView.tintColor = color
rightView = imageView
} else {
rightViewMode = UITextFieldViewMode.never
rightView = nil
}
// Placeholder text color
attributedPlaceholder = NSAttributedString(string: placeholder != nil ? placeholder! : "", attributes:[NSForegroundColorAttributeName: color])
if(self.isUnderLine)
{
let underline:UIView = UIView()
underline.frame = CGRect(x: 0, y: self.frame.size.height-1, width: self.frame.size.width, height: 1)
underline.backgroundColor = underlineColor
self.addSubview(underline)
}
}
override func textRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(x: textLeftPadding, y: 0, width: bounds.width, height: bounds.height)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return self.textRect(forBounds: bounds)
}
}
and using validation as
if (txtField.text != "condtion" ){
txtField.setError(error:"Error message for text field");
valid = false;
txtField.becomeFirstResponder()
}

UITextfield gets cut from the top while in editing mode

I got two UITextfields with separator(UILabel) between them.
I put them all into UIStackView.
While in editing mode, content of the textfield is cut from the top, as seen in the picture below
I've found that the only way to remove this issue is to make this separator big enough, but this spoils my design.
How to fix it?
It's worth to mention my UIStackView settings:
and show how I implement this custom bottomline-style UITextfield
class CustomTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
let attributedString = NSAttributedString(string: self.placeholder!, attributes: [NSForegroundColorAttributeName:UIColor.lightGray, NSFontAttributeName: UIFont(name: "GothamRounded-Book", size: 18.0)! ])
self.attributedPlaceholder = attributedString
self.tintColor = UIColor.appRed
self.font = UIFont(name: "GothamRounded-Book", size: 18.0)!
self.borderStyle = .none
self.textAlignment = .center
}
override func textRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: 0, dy: 5)
}
override func editingRect(forBounds bounds: CGRect) -> CGRect {
return bounds.insetBy(dx: 0, dy: 5)
}
override var tintColor: UIColor! {
didSet {
setNeedsDisplay()
}
}
override func draw(_ rect: CGRect) {
let startingPoint = CGPoint(x: rect.minX, y: rect.maxY)
let endingPoint = CGPoint(x: rect.maxX, y: rect.maxY)
let path = UIBezierPath()
path.move(to: startingPoint)
path.addLine(to: endingPoint)
path.lineWidth = 2.0
tintColor.setStroke()
tintColor = UIColor.appRed
path.stroke()
}
}
Any help much appreciated
EDIT
I have another TextField like that and it works fine, but it doesn't sit inside any horizontal UIStackView. Here is the screenshot of hierarchy:
Unfortunately you need to check the size on editing
class CustomTextField: UITextField {
override func awakeFromNib() {
super.awakeFromNib()
self.addTarget(self, action: #selector(textFieldEditingChanged), for: .editingChanged)
}
func textFieldEditingChanged(_ textField: UITextField) {
textField.invalidateIntrinsicContentSize()
}
override var intrinsicContentSize: CGSize {
if isEditing {
let string = text ?? ""
let size = string.size(attributes: typingAttributes)
return CGSize(width: size.width + (rightView?.bounds.size.width ?? 0) + (leftView?.bounds.size.width ?? 0) + 2,
height: size.height)
}
return super.intrinsicContentSize
}
}

Setting the frame of a sublayer inside of layoutSubviews() makes changing it impossible. Any workaround for that?

I'm working on a customizable UITextField (see code below). I have added a border at the bottom (you can set it in the storyboard). However, I had problems setting the frame of the CALayer that this border consists of.
If I set it inside the didSet method of var showBottomBorder it doesn't appear on the screen. I think this is because the frame (of the UITextField) hasn't been calculated yet (maybe didSet gets called before that).
So I moved it to the layoutSubviews() method (see code below). This works perfectly.
But now I have another problem. I can't really change that frame anymore. Every time I change it, it gets reset by layoutSubviews() which I think is called then.
At the bottom of my code, there is the method textFieldDidBeginEditing. In there, I wanted to move up my bottom border (animated). But it doesn't work. The border does not move anywhere. And like I said, I think it's because I set the frame inside the layoutSubviews() method.
Is there a better way to set the frame of the bottom border? A way which allows me to change stuff?
#IBDesignable
class CustomizableTextField: UITextField, UITextFieldDelegate {
// MARK: - Properties
private var bottomBorder = CALayer()
// MARK: - #IBInspectables
#IBInspectable var roundCorners: CGFloat = 0 {
didSet {
self.layer.cornerRadius = roundCorners
self.clipsToBounds = true
}
}
/** -- */
#IBInspectable var borderWidth: CGFloat = 1.0 {
didSet {
self.layer.borderWidth = self.borderWidth
}
}
#IBInspectable var borderColor: UIColor = UIColor.white {
didSet {
self.layer.borderColor = self.borderColor.cgColor
}
}
/** -- */
/** -- */
private var showBottomBorder: Bool = false {
didSet {
switch showBottomBorder {
case true:
bottomBorder.borderColor = self.bottomBorderColor.cgColor
bottomBorder.borderWidth = self.bottomBorderWidth
self.layer.addSublayer(bottomBorder)
self.layer.masksToBounds = true
break
case false:
bottomBorder.removeFromSuperlayer()
break
}
}
}
#IBInspectable var bottomBorderWidth: CGFloat = 1.0 {
didSet {
self.showBottomBorder = false
self.showBottomBorder = true
}
}
#IBInspectable var bottomBorderColor: UIColor = UIColor.white {
didSet {
self.showBottomBorder = false
self.showBottomBorder = true
}
}
/** -- */
/** -- */
// Somwhow, the default panel for my font color doesn't change anything, so I created this
#IBInspectable var fixedFontColor: UIColor = UIColor.white {
didSet {
self.textColor = fixedFontColor
}
}
#IBInspectable var placeholderFontColor: UIColor = UIColor.white {
didSet {
var placeholderTxt = ""
if let txt = self.placeholder {
placeholderTxt = txt
}
self.attributedPlaceholder = NSAttributedString(string: placeholderTxt, attributes: [NSForegroundColorAttributeName: placeholderFontColor])
}
}
/** -- */
// MARK: - Overrides and Initializers
override init(frame: CGRect) {
super.init(frame: frame)
}
override func layoutSubviews() {
super.layoutSubviews()
// HERE
bottomBorder.frame = CGRect(x: 0, y: self.frame.size.height - self.bottomBorderWidth, width: self.frame.size.width, height: self.frame.size.height)
}
// setting the textField delegate to self
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//self.borderStyle = .none
self.delegate = self
}
// MARK: - Events
func textFieldDidBeginEditing(_ textField: UITextField) {
}
You can use and extension on UITextFiled for setting the border.
And keep a reference to it with KVC.
By overriding LayoutSubview, every the layout will change, we'l check if the border exists, if so remove it, and re-create a new one with the new frame:
import UIKit
let MyTopBorder = "myTopBorder"
let MyBottomBorder = "myBottomBorder"
struct Defaults {
static let width = CGFloat(1.0)
static func bottonBorderFrame(view: UIView)->CGRect {
return CGRect(x: CGFloat(0), y: view.frame.size.height - Defaults.width, width: view.frame.size.width, height: view.frame.size.height)
}
static func topBorderFrame(view: UIView)->CGRect {
return CGRect(x: CGFloat(0), y: CGFloat(0) , width: view.frame.size.width, height: Defaults.width)
}
}
extension UITextField
{
func setBottomBorder(color:CGColor)
{
if let isBottomBorder = self.getBottomBorderIfExists() {
isBottomBorder.removeFromSuperlayer()
}
self.setBorderWithFrame(Defaults.bottonBorderFrame(self), color: color, andKey: MyBottomBorder)
}
func setTopBorder(color:CGColor)
{
if let isTopBorder = self.getTopBorderIfExists() {
isTopBorder.removeFromSuperlayer()
}
self.setBorderWithFrame(Defaults.topBorderFrame(self), color: color, andKey: MyTopBorder)
}
func setBorderWithFrame(frame: CGRect, color: CGColor, andKey: String) {
self.borderStyle = UITextBorderStyle.None;
let border = CALayer()
border.borderColor = color
border.frame = frame
border.borderWidth = Defaults.width
self.layer.addSublayer(border)
self.layer.masksToBounds = true
self.layer.setValue(border, forKey: andKey)
}
func removeTopBorder() {
if let isTopBorder = self.getTopBorderIfExists() {
self.layer.setValue(nil, forKey: MyTopBorder)
isTopBorder.removeFromSuperlayer()
}
}
func removeBottomBorder() {
if let isBottomBorder = self.getBottomBorderIfExists() {
self.layer.setValue(nil, forKey: MyBottomBorder)
isBottomBorder.removeFromSuperlayer()
}
}
private func getBorderIfExistsByKey(key: String)->CALayer? {
if let isBorderSet = self.layer.valueForKey(key) {
if let borderIsCALayer = isBorderSet as? CALayer {
return borderIsCALayer
}
}
return nil
}
private func getTopBorderIfExists()->CALayer? {
return self.getBorderIfExistsByKey(MyTopBorder)
}
private func getBottomBorderIfExists()->CALayer? {
return self.getBorderIfExistsByKey(MyBottomBorder)
}
public override func layoutSubviews() {
super.layoutSubviews()
// Update bottom on frame change
if let isBottomBorder = self.getBottomBorderIfExists() {
let borderColor = isBottomBorder .borderColor
self.removeBottomBorder()
self.setBottomBorder(borderColor!)
}
// Update top on frame change
if let isTopBorder = self.getTopBorderIfExists() {
let borderColor = isTopBorder.borderColor
self.removeTopBorder()
self.setTopBorder(borderColor!)
}
}
}
Usage:
let textField = UITextField(frame: CGRect(x: 100,y: 100, width: 100, height: 100))
textField.backgroundColor = UIColor.blueColor() // Thie color is for visulizing better
self.view.addSubview(textField)
textField.setBottomBorder(UIColor.blackColor().CGColor) // Now you have a border
textField.frame = CGRect(x: 150, y: 200, width: 200, height: 200) // And the border updated to the new frame
// Now if you would like to change from bottom to top, simply do this:
textField.removeBottomBorder()
textField.setTopBorder(UIColor.blackColor().CGColor)

Resources