UITextField is part of UIKit framework and is used to display an area to collect text input from the user using the onscreen keyboard. When a text field is tapped, the keyboard automatically slides up onto the screen. (You will see why this happens later in this chapter.) The keyboard’s appearance is determined by a set of the UITextField’s properties called the UITextInputTraits
. One of these properties is the type of keyboard that is displayed.
Initialize text field
let frame = CGRect(x: 0, y: 0, width: 100, height: 100) let textField = UITextField(frame: frame)
The default event for text fields is .editingDidBegin
, which is triggered when the field is tapped. This is not the event you are interested in. Instead, you are interested in the .editingChanged
event, which is triggered when a change is made to the field.
Currently, there is no way to . Let’s add that functionality. One common way of dismissing the keyboard is by detecting when the user taps the Return key and using that action to dismiss the keyboard.
When the text field is tapped, the method becomeFirstResponder()
is called on it. This is the method that, among other things, causes the keyboard to appear. To dismiss the keyboard, you call the method resignFirstResponder()
on the text field.
Implement an action method that will dismiss the keyboard when called.
@IBAction func dismissKeyboard(_ sender: UITapGestureRecognizer) { textField.resignFirstResponder() }
Now you need a way of triggering the method you implemented. You will use a gesture recognizer to accomplish this.
A gesture recognizer is a subclass of UIGestureRecognizer
that detects a specific touch sequence and calls an action on its target when that sequence is detected. There are gesture recognizers that detect taps, swipes, long presses, and more.
In Main.storyboard, find Tap Gesture Recognizer in the library. Drag this object onto the background view for the view controller. You will see a reference to this gesture recognizer in the scene dock, the row of icons above the scene in the canvas.
Control-drag from the gesture recognizer in the scene dock to the view controller and connect it to the dismissKeyboard
method.
When the user types into a text field, that text field will ask its delegate if it wants to accept the changes that the user has made. The first step is enabling instances of the ViewController class to perform the role of UITextField delegate by declaring that ViewController conforms to the UITextFieldDelegate
protocol.
class MovieAddViewController: UIViewController, UITextFieldDelegate { }
Open Main.storyboard and Control-drag from the text field to the view controller. Choose delegate from the panel to connect the delegate property of the text field to the ViewController.
Next, you are going to implement the UITextFieldDelegate
method that you are interested in textField(_:shouldChangeCharactersIn:replacementString:)
. Because the text field calls this method on its delegate, you must implement it in ViewController.swift.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let text = (tfOutlet.text! as NSString).replacingCharacters(in: range, with: string) print("Current text: \(textField.text)") print("Replacement text: \(string)") print("New text: \(text)") let existingTextHasDecimalSeparator = textField.text?.range(of: ".") let replacementTextHasDecimalSeparator = string.range(of: ".") if existingTextHasDecimalSeparator != nil, replacementTextHasDecimalSeparator != nil { return false } else { return true } //return true }
Character limit on UITextField
The first thing that needs to get done is to make ViewController adopt and conform to UITextFieldDelegate
. Let's do that now.
class ViewController: UIViewController, UITextFieldDelegate { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { return self.textLimit(existingText: textField.text, newText: string, limit: 10) } private func textLimit(existingText: String?, newText: String, limit: Int) -> Bool { let text = existingText ?? "" let isAtLimit = text.count + newText.count <= limit return isAtLimit } }
Input accessory view toolbar
Add an accessory view above the keyboard. This is commonly used for adding next/previous buttons, or additional buttons like Done/Submit (especially for the number/phone/decimal pad keyboard types which don’t have a built-in return key).
let textField = UITextField() let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: 44.0) let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .FlexibleSpace, target: nil, action: nil) let doneButton = UIBarButtonItem(barButtonSystemItem: .Done, target: self, action: Selector("done")) let items = [flexibleSpace, doneButton] // pushes done button to right side toolbar.setItems(items, animated: false) toolbar.sizeToFit() textField.inputAccessoryView = toolbar
Padding in UITextField
extension UITextField { func setLeftPadding(padding: CGFloat) { let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: padding, height: self.frame.size.height)) self.leftView = paddingView self.leftViewMode = .always } func setRightPadding(padding: CGFloat) { let paddingView = UIView(frame: CGRect(x: 0, y: 0, width: padding, height: self.frame.size.height)) self.rightView = paddingView self.rightViewMode = .always } }
Usage
myTextField.setLeftPadding(padding: 20)
Change placeholder color and font
We can change the style of the placeholder by setting attributedPlaceholder
(a NSAttributedString).
var placeholderAttributes = [String: AnyObject]() placeholderAttributes[NSAttributedString.Key.foregroundColor] = UIColor.red placeholderAttributes[NSFontAttributeName] = font if let placeholder = textField.placeholder { let newAttributedPlaceholder = NSAttributedString(string: placeholder, attributes: placeholderAttributes) textField.attributedPlaceholder = newAttributedPlaceholder }
In this example we change only the color and font. You could change other properties such as underline or strikethrough style. Refer to NSAttributedString for the properties that can be changed.
Custom UITextField for Filtering Input Text
Here is an example of custom UITextField that takes only numerical text and discards all other.
class NumberTextField: UITextField { required init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) registerForTextFieldNotifications() } override init(frame: CGRect) { super.init(frame: frame) } override func awakeFromNib() { super.awakeFromNib() keyboardType = .numberPad//useful for iPhone only } private func registerForTextFieldNotifications() { NotificationCenter.default.addObserver(self, selector: #selector(NumberTextField.textDidChange), name: NSNotification.Name(rawValue: "UITextFieldTextDidChangeNotification"), object: self) } deinit { NotificationCenter.default.removeObserver(self) } func textDidChange() { text = filteredText() } private func filteredText() -> String { let inverseSet = CharacterSet(charactersIn:"0123456789").inverted let components = text!.components(separatedBy: inverseSet) return components.joined(separator: "") } }
For filtering you can override shouldChangeCharactersIn
method
let allowedCharacters = CharacterSet(charactersIn:"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvxyz").inverted func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { let components = string.components(separatedBy: allowedCharacters) let filtered = components.joined(separator: "") if string == filtered { return true } else { return false } }
Add UIDatePicker to UITextField
We wil use inputView
property of UItextField. inputView
property, as per apple documentation
If the value in this property is nil, the text field displays the standard system keyboard when it becomes first responder. Assigning a custom view to this property causes that view to be presented instead.
So, if we specify UIDatePicker as inputview to UItextField then system will not present keyboard to user and will show custom view, which in our case is UIDatePicker. We will use extension class to add UIDatePicker to UITextField.
extension UITextField { func setDatePickerAsInputViewFor(target:Any, selector:Selector) { let screenWidth = UIScreen.main.bounds.width let datePicker = UIDatePicker(frame: CGRect(x: 0.0, y: 0.0, width: screenWidth, height: 200.0)) datePicker.datePickerMode = .date self.inputView = datePicker let toolBar = UIToolbar(frame: CGRect(x: 0.0, y: 0.0, width: screenWidth, height: 40.0)) let cancel = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(tapCancel)) let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) let done = UIBarButtonItem(title: "Done", style: .done, target: nil, action: selector) toolBar.setItems([cancel,flexibleSpace, done], animated: false) self.inputAccessoryView = toolBar } @objc func tapCancel() { self.resignFirstResponder() } }
Usage
override func viewDidLoad() { super.viewDidLoad() self.txtDate.setDatePickerAsInputViewFor(target: self, selector: #selector(dateSelected)) } @objc func dateSelected() { if let datePicker = self.txtDate.inputView as? UIDatePicker { let dateFormatter = DateFormatter() dateFormatter.dateStyle = .medium self.txtDate.text = dateFormatter.string(from: datePicker.date) } self.txtDate.resignFirstResponder() }
OTPView
OTPView.swift file.
import UIKit class OTPView: UIStackView { var textFieldArray = [OTPTextField]() var numberOfOTPdigit = 4 override init(frame: CGRect) { super.init(frame: frame) setupStackView() setTextFields() } required init(coder: NSCoder) { super.init(coder: coder) setupStackView() setTextFields() } private func setupStackView() { self.backgroundColor = .clear self.isUserInteractionEnabled = true self.translatesAutoresizingMaskIntoConstraints = false self.contentMode = .center self.distribution = .fillEqually self.spacing = 5 } private func setTextFields() { for i in 0..<numberOfOTPdigit { let field = OTPTextField() textFieldArray.append(field) addArrangedSubview(field) field.delegate = self field.backgroundColor = .lightGray field.layer.opacity = 0.5 field.textAlignment = .center field.layer.shadowColor = UIColor.black.cgColor field.layer.shadowOpacity = 0.1 i != 0 ? field.previousTextField = textFieldArray[i-1] : nil i != 0 ? textFieldArray[i-1].nextTextFiled = textFieldArray[i] : nil } } } extension OTPView: UITextFieldDelegate { func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { guard let field = textField as? OTPTextField else { return true } if !string.isEmpty { field.text = string field.resignFirstResponder() field.nextTextFiled?.becomeFirstResponder() return true } return true } } class OTPTextField: UITextField { var previousTextField: UITextField? var nextTextFiled: UITextField? override func deleteBackward() { text = "" previousTextField?.becomeFirstResponder() } }
ViewController.swift file.
import UIKit import SnapKit class ViewController: UIViewController { lazy var otpView: OTPView = { let v = OTPView() return v }() lazy var btn: UIButton = { let btn = UIButton(type: .system) btn.contentEdgeInsets = UIEdgeInsets(top: 8, left: 16, bottom: 8, right: 16) btn.setTitle("Verify", for: .normal) btn.layer.cornerRadius = 8.0 btn.tintColor = .white btn.backgroundColor = .red btn.addTarget(self, action: #selector(onVerifyTapped), for: .touchUpInside) return btn }() override func viewDidLoad() { super.viewDidLoad() setupUI() } @objc func onVerifyTapped(sender: UIButton!) { } func setupUI() { self.view.addSubview(otpView) self.view.addSubview(btn) otpView.snp.makeConstraints { (make) -> Void in make.size.equalTo(CGSize(width: 200, height: 50)) make.center.equalTo(self.view) } btn.snp.makeConstraints { (make) -> Void in make.top.equalTo(otpView.snp.bottom).offset(16) make.centerX.equalTo(self.view) } } }