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

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;

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

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