Add bottom border line to UI TextField view in SwiftUI / Swift / Objective-C / Xamarin

147,942

Solution 1

I am creating custom textField to make it reusable component for SwiftUI

SwiftUI

struct CustomTextField: View {
    var placeHolder: String
    @Binding var value: String
    
    var lineColor: Color
    var width: CGFloat
    
    var body: some View {
        VStack {
            TextField(self.placeHolder, text: $value)
            .padding()
            .font(.title)
            
            Rectangle().frame(height: self.width)
                .padding(.horizontal, 20).foregroundColor(self.lineColor)
        }
    }
}

Usage:

@Binding var userName: String
@Binding var password: String

var body: some View {
    VStack(alignment: .center) {
        CustomTextField(placeHolder: "Username", value: $userName, lineColor: .white, width: 2)
        CustomTextField(placeHolder: "Password", value: $password, lineColor: .white, width: 2)
    }
}


Swift 5.0

I am using Visual Formatting Language (VFL) here, This will allow adding a line to any UIControl.

You can create a UIView extension class like UIView+Extention.swift

import UIKit

enum LinePosition {
    case top
    case bottom
}

extension UIView {
    func addLine(position: LinePosition, color: UIColor, width: Double) {
        let lineView = UIView()
        lineView.backgroundColor = color
        lineView.translatesAutoresizingMaskIntoConstraints = false // This is important!
        self.addSubview(lineView)

        let metrics = ["width" : NSNumber(value: width)]
        let views = ["lineView" : lineView]
        self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[lineView]|", options:NSLayoutConstraint.FormatOptions(rawValue: 0), metrics:metrics, views:views))

        switch position {
        case .top:
            self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:|[lineView(width)]", options:NSLayoutConstraint.FormatOptions(rawValue: 0), metrics:metrics, views:views))
            break
        case .bottom:
            self.addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "V:[lineView(width)]|", options:NSLayoutConstraint.FormatOptions(rawValue: 0), metrics:metrics, views:views))
            break
        }
    }
}

Usage:

textField.addLine(position: .LINE_POSITION_BOTTOM, color: .darkGray, width: 0.5)

Objective C:

You can add this helper method to your global helper class(I used global class method) or in the same view controller(using an instance method).

typedef enum : NSUInteger {
    LINE_POSITION_TOP,
    LINE_POSITION_BOTTOM
} LINE_POSITION;


- (void) addLine:(UIView *)view atPosition:(LINE_POSITION)position withColor:(UIColor *)color lineWitdh:(CGFloat)width {
    // Add line
    UIView *lineView = [[UIView alloc] init];
    [lineView setBackgroundColor:color];
    [lineView setTranslatesAutoresizingMaskIntoConstraints:NO];
    [view addSubview:lineView];
    
    NSDictionary *metrics = @{@"width" : [NSNumber numberWithFloat:width]};
    NSDictionary *views = @{@"lineView" : lineView};
    [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[lineView]|" options: 0 metrics:metrics views:views]];
    
    switch (position) {
        case LINE_POSITION_TOP:
            [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-0-[lineView(width)]" options: 0 metrics:metrics views:views]];
            break;
            
        case LINE_POSITION_BOTTOM:
            [view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[lineView(width)]|" options: 0 metrics:metrics views:views]];
            break;
        default: break;
    }
}

Usage:

[self addLine:self.textField atPosition:LINE_POSITION_TOP withColor:[UIColor darkGrayColor] lineWitdh:0.5];

Xamarin code:

 var border = new CALayer();
 nfloat width = 2;
 border.BorderColor = UIColor.Black.CGColor;
 border.Frame = new CoreGraphics.CGRect(0, textField.Frame.Size.Height - width, textField.Frame.Size.Width, textField.Frame.Size.Height);
 border.BorderWidth = width;
 textField.Layer.AddSublayer(border);
 textField.Layer.MasksToBounds = true;

Solution 2

If you want to do without knowing frames beforehand, without subclassing and without Autolayout:

Swift 5 / Swift 4.x / Swift 3.x

extension UITextField {
  func setBottomBorder() {
    self.borderStyle = .none
    self.layer.backgroundColor = UIColor.white.cgColor

    self.layer.masksToBounds = false
    self.layer.shadowColor = UIColor.gray.cgColor
    self.layer.shadowOffset = CGSize(width: 0.0, height: 1.0)
    self.layer.shadowOpacity = 1.0
    self.layer.shadowRadius = 0.0
  }
}

Call as yourTextField.setBottomBorder() from anywhere without making sure of the frames to be right.

The Result looks like this:

sample

Swift UI

struct MyTextField: View {
  var myPlaceHolder: String
  @Binding var text: String

  var underColor: Color
  var height: CGFloat

  var body: some View {
    VStack {
        TextField(self.myPlaceHolder, text: $text)
        .padding()
        .font(.title)

        Rectangle().frame(height: self.height)
            .padding(.horizontal, 24).foregroundColor(self.underColor)
    }
  }
}

Solution 3

You can create a subclass of UITextField as shown below:

class TextField : UITextField {

    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()

        path.stroke()
    }
}

Solution 4

None of these solutions really met my expectations. I wanted to subclass the TextField since I don't want to set the border manually all the time. I also wanted to change the border color e.g. for an error. So here's my solution with Anchors:

class CustomTextField: UITextField {

    var bottomBorder = UIView()

    override func awakeFromNib() {

            // Setup Bottom-Border

            self.translatesAutoresizingMaskIntoConstraints = false

            bottomBorder = UIView.init(frame: CGRect(x: 0, y: 0, width: 0, height: 0))
            bottomBorder.backgroundColor = UIColor(rgb: 0xE2DCD1) // Set Border-Color
            bottomBorder.translatesAutoresizingMaskIntoConstraints = false

            addSubview(bottomBorder)

            bottomBorder.bottomAnchor.constraint(equalTo: bottomAnchor).isActive = true
            bottomBorder.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
            bottomBorder.rightAnchor.constraint(equalTo: rightAnchor).isActive = true
            bottomBorder.heightAnchor.constraint(equalToConstant: 1).isActive = true // Set Border-Strength

    }
}

---- Optional ----

To change the color add sth like this to the CustomTextField Class:

@IBInspectable var hasError: Bool = false {
    didSet {

        if (hasError) {

            bottomBorder.backgroundColor = UIColor.red

        } else {

            bottomBorder.backgroundColor = UIColor(rgb: 0xE2DCD1)

        }

    }
}

And to trigger the Error call this after you created an instance of CustomTextField

textField.hasError = !textField.hasError

enter image description here

Hope it helps someone ;)

Solution 5

 extension UITextField {  
  func setBottomBorder(color:String) {
    self.borderStyle = UITextBorderStyle.None
    let border = CALayer()
    let width = CGFloat(1.0)
    border.borderColor = UIColor(hexString: color)!.cgColor
    border.frame = CGRect(x: 0, y: self.frame.size.height - width,   width:  self.frame.size.width, height: self.frame.size.height)
    border.borderWidth = width
    self.layer.addSublayer(border)
    self.layer.masksToBounds = true
   }
}

and then just do this:

yourTextField.setBottomBorder(color: "#3EFE46")
Share:
147,942

Related videos on Youtube

dhaval shah
Author by

dhaval shah

Updated on August 30, 2021

Comments

  • dhaval shah
    dhaval shah almost 3 years

    I would like to keep the border at the bottom part only in UITextField. But I don't know how we can keep it on the bottom side.

    Can you please advise me?

  • dhaval shah
    dhaval shah over 9 years
    i ll have a look at this one saurabh.But it ll be great of we can make it by code?Do u have any idea?
  • iAkshay
    iAkshay over 7 years
    I was thinking to do like this, but if we use this in viewDidLoad(), the frame would be incorrect. So we have 2 choices : viewDidLayoutSubviews() or viewDidAppear(). But viewDidLayoutSubviews() called multiple times and calling from viewDidAppear() would not be good experience.
  • theDC
    theDC over 7 years
    Does it really work? I'm not getting any bottom line
  • saurabh
    saurabh over 7 years
    It works for me, can you show some of your code, so we can look into
  • theDC
    theDC over 7 years
    I call it inside awakeFromNib() method and it does not return any bottom line. Same for calling it in layoutSubviews()
  • saurabh
    saurabh over 7 years
    Try in viewDidLoad()?
  • theDC
    theDC over 7 years
    I would need to set outlets for my textfields, I'd prefer to do this inside textField class
  • saurabh
    saurabh over 7 years
    @DCDC: I ran into this problem while using background Color for textField, so I removed the background color and it worked.
  • Kunal Kumar
    Kunal Kumar over 7 years
    viewDidLayoutSubviews() will also won't work if the textfield is nested inside multiple View. You'll get multiple broder.
  • Deepak Chaudhary
    Deepak Chaudhary over 7 years
    Best approach to do. I just wanted to know how to change underline color while editing or during the method "didBeginEditing" and change color on "didEndEditing"
  • user1046037
    user1046037 over 7 years
    See the updated answer, set the tintColor in didBeginEditing and didEndEditing
  • markhorrocks
    markhorrocks about 7 years
    This didn't work for me, I set it in viewDidLoad()
  • markhorrocks
    markhorrocks about 7 years
    This is the solution I used. I subtracted 4 from maxY to move the underline closer to the text entry.
  • saurabh
    saurabh about 7 years
    @markhorrocks Can you share your result? I tried it and it worked for me.
  • Satyam
    Satyam about 7 years
    If we change the background color to clear color, its not working.
  • saurabh
    saurabh about 7 years
    @Satyam Background color of UITextField?
  • Satyam
    Satyam about 7 years
    yes. If we change white color to clear color in self.layer.backgroundColor = UIColor.white.cgColor its not working
  • C0D3
    C0D3 almost 7 years
    I think in Swift 4.0 you need to change the "set" to "didSet" but otherwise it works, thank you
  • cheznead
    cheznead almost 7 years
    This only worked for me if you call it in viewDidLayoutSubviews() and not viewDidLoad(). Was that the intention here?
  • Bruno Ferreira
    Bruno Ferreira over 6 years
    Best solution so far, you can even modify it for other "validation" states
  • Serdnad
    Serdnad almost 6 years
    Great solution, works with a clear background unlike some of the others.
  • GordonW
    GordonW over 5 years
    remember to add func call to viewDidLayoutSubviews() if using auto layout :) otherwise your line will not correctly match frame.
  • timetraveler90
    timetraveler90 over 4 years
    Is there an easy way to remove this line after setting it? For example, I want to have it while my textField is active else I'd revert it to the default style.
  • Ilesh P
    Ilesh P over 4 years
    yeah, simply hold object any remove or hide/show when you need it. :)
  • Abhi Kapoor
    Abhi Kapoor over 4 years
    Work for me in swift 4.2
  • zach wilcox
    zach wilcox over 3 years
    Can you make this work with just a simple textView?