iOS Week: Custom UITextFields

ezgif-1-28258ef67c.gif

Let’s see how we would make this text field in Swift

First create a UITextField subclass. I called mine UnderlinedTextField and annotated it as @IBDesignable. Now create a few properties.

Screen Shot 2018-03-27 at 15.55.30

These are annotated as @IBInspectable so they can be set in interface builder. We also need a property of hasError, which when set changes the colour of the text field:

Screen Shot 2018-03-27 at 15.56.35.png

When this property is set, we set the colour of our placeholderView. This is just a UILabel that we add and we animate it up when the user starts typing. Then if hasError is true, the text colour turns red and the view redraws to turn the underline colour to red. If it’s set again and is true again, the label will shake using a CAKeyframeAnimation.

Now we just override draw(_ rect: CGRect) and setup the placeholderView in didMoveToWindow(). That’s it, in about 100 lines of code. You can grab everything down below to look at in a bit more detail.

import UIKit
@IBDesignable
class UnderlinedTextField: UITextField {
@IBInspectable var borderColor: UIColor = Colors.darkText {
didSet { setNeedsDisplay() }
}
@IBInspectable var borderedWidth: CGFloat = 1 {
didSet { setNeedsDisplay() }
}
@IBInspectable var padding: CGFloat = 0 {
didSet { setNeedsDisplay() }
}
@IBInspectable var errorColor: UIColor = Colors.errorBackground {
didSet { setNeedsDisplay() }
}
var hasError: Bool = false {
didSet {
if hasError && hasError == oldValue {
let animation = CAKeyframeAnimation(keyPath: "transform.translation.x")
animation.duration = 0.3
animation.repeatCount = 1
animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
animation.values = [-10.0, 10.0, -5.0, 5.0, -2.0, 0.0]
placeholderView.layer.add(animation, forKey: "shake")
return
}
setNeedsDisplay()
placeholderView.textColor = hasError ? errorColor : borderColor
}
}
override var intrinsicContentSize: CGSize {
let size = super.intrinsicContentSize
return CGSize(width: size.width, height: size.height + 30)
}
lazy var placeholderView: UILabel = {
let placeholderView = UILabel()
placeholderView.textColor = borderColor
placeholderView.font = Font.semiBold.withSize(10)
placeholderView.text = placeholder ?? "No text"
placeholderView.alpha = 0.0
return placeholderView
}()
var placeholderViewTopConstraint: NSLayoutConstraint?
override func draw(_ rect: CGRect) {
let path = UIBezierPath()
path.move(to: CGPoint(x: rect.minX + padding, y: rect.maxY))
path.addLine(to: CGPoint(x: rect.maxX padding, y: rect.maxY))
path.lineWidth = borderedWidth
if hasError {
errorColor.setStroke()
} else {
borderColor.setStroke()
}
path.stroke()
}
override func didMoveToWindow() {
super.didMoveToWindow()
font = Font.semiBold.withSize(17)
addSubview(placeholderView)
placeholderView.translatesAutoresizingMaskIntoConstraints = false
placeholderView.leadingAnchor.constraint(equalTo: leadingAnchor).isActive = true
placeholderViewTopConstraint = placeholderView.topAnchor.constraint(equalTo: topAnchor, constant: 5)
placeholderViewTopConstraint?.priority = UILayoutPriority(999)
placeholderViewTopConstraint?.isActive = true
addTarget(self, action: #selector(textDidChange), for: .editingChanged)
}
@objc func textDidChange() {
if text != "" {
placeholderViewTopConstraint?.constant = 0
UIView.animate(withDuration: 0.25) {
self.placeholderView.alpha = 1.0
self.layoutIfNeeded()
}
} else {
placeholderViewTopConstraint?.constant = 5
UIView.animate(withDuration: 0.25) {
self.placeholderView.alpha = 0.0
self.layoutIfNeeded()
}
}
}
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s