Even though forms aren't difficult to implement on iOS, they can be frustrating to use. In today's tutorial, I'd like to share five simple tips that can drastically improve the usability of the forms in your iOS applications.
Setting Up the Project
Let's get started by launching Xcode and creating a project using the Single View Application template.
Name the project Forms and set Language to Swift.
Creating the User Interface
In this tutorial, we build a simple registration form with four text fields. Open ViewController.swift and declare four outlets of type UITextField
.
import UIKit
class ViewController: UIViewController {
// MARK: - Properties
@IBOutlet var firstNameTextField: UITextField!
@IBOutlet var lastNameTextField: UITextField!
@IBOutlet var passwordTextField: UITextField!
@IBOutlet var emailTextField: UITextField!
}
Open Main.storyboard, add a vertical stack view to the View Controller Scene, and add a label, four text fields, and a button to the stack view. With the stack view selected, set Spacing to 8. Set the text of the label to Create an Account and set the title of the button to Save. This is the form you should end up with.
Don't forget to connect each text field with its corresponding outlet. Ready? It's time for the first tip.
Tip 1: Taking Advantage of the Return Key
The first tip is simple, taking advantage of the return key. When the user taps the return key in the bottom right, the application should automatically make the next text field the first responder. This is very easy to do.
To make this work, the view controller needs to be the delegate of the text fields. In Main.storyboard, select each text field and set the text field's delegate
property to the view controller of the scene.
Open ViewController.swift, create an extension for the ViewController
class, and conform the ViewController
class to the UITextFieldDelegate
protocol. The only method we need to implement for now is textFieldShouldReturn(_:)
.
extension ViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
}
}
In this method, we make the next text field of the form the first responder every time the user taps the return key. When the user taps the return key with the last text field as the first responder, we dismiss the keyboard by invoking resignFirstResponder()
on the text field. We use a simple switch
statement to make this work.
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
switch textField {
case firstNameTextField:
lastNameTextField.becomeFirstResponder()
case lastNameTextField:
passwordTextField.becomeFirstResponder()
case passwordTextField:
emailTextField.becomeFirstResponder()
default:
emailTextField.resignFirstResponder()
}
return true
}
Build and run the application to give it a try. This is a good first step to making the registration form a bit more user friendly.
Tip 2: Inform the User
Subtle details can make a form more user friendly with very little effort. For example, each text field should have a placeholder that informs the user what they should enter into the text field. You can also add a label to each text field or provide a short message to help the user fill out the form.
What often annoys me about forms is that text fields are left at their defaults. If I enter my name in a form, I don't want to be corrected by the operating system. If the user needs to enter their email address in a text field, it's important that the keyboard displayed to the user makes this painless.
Open Main.storyboard and add a placeholder to each text field. You can add whatever placeholder you want, but make sure you go for usability. Don't be too formal and don't be too clever.
I prefer to disable correction and spell checking for text fields that may contain unexpected input, such as the person's first and last name, a password, or a city. Select the first name text field and disable Correction and Spell Checking. Repeat this step for the other text fields.
Select the email text field and set Keyboard Type to E-mail Address. Because the email text field is the last text field of the form, set Return Key to Done. This is another a subtle hint to the user.
Tip 3: Security Is Key
The SDK makes it easy to apply security best practices. It's important to keep those in mind. Select the password text field and, in the Attributes Inspector, check the Secure Text Entry checkbox. The operating system takes care of the rest.
Tip 4: Show Visual Cues
Form validation is another key component of a usable form. Not only do you need to validate the user's input before sending it to a backend, you need to tell the user if something's wrong with their input and how they can fix it.
Let's take the password text field as an example. I'd like to show an error message if the password is weak. Open ViewController.swift and declare an outlet, passwordValidationLabel
, of type UILabel
. This label shows the error message to the user if the password isn't strong.
import UIKit
class ViewController: UIViewController {
// MARK: - Properties
@IBOutlet var firstNameTextField: UITextField!
@IBOutlet var lastNameTextField: UITextField!
@IBOutlet var passwordTextField: UITextField!
@IBOutlet var emailTextField: UITextField!
@IBOutlet var passwordValidationLabel: UILabel!
}
Open Main.storyboard and insert a label below the password text field. Connect the label to the passwordValidationLabel
outlet.
We're going to implement a simple validation method that is triggered when the user taps the return or done key. At the moment, we only validate the password text field. This is what the validate(_:)
method looks like.
// MARK: - Helper Methods
fileprivate func validate(_ textField: UITextField) -> (Bool, String?) {
guard let text = textField.text else {
return (false, nil)
}
if textField == passwordTextField {
return (text.characters.count >= 6, "Your password is too short.")
}
return (text.characters.count > 0, "This field cannot be empty.")
}
The validate(_:)
method accepts a UITextField
instance as its only argument and return a tuple of type (Bool, String?)
. As I mentioned, we're only validating the password text field at the moment. The tuple the method returns includes the result of the validation as well as an optional error message to show to the user.
We also need to update the textFieldShouldReturn(_:)
method of the UITextFieldDelegate
protocol. If the password text field is the first responder when the user taps the return key, we validate the text field's text. We only make the email text field the first responder if validation succeeds. We also update the password validation label with the error message and show or hide the password validation label.
extension ViewController: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
switch textField {
case firstNameTextField:
lastNameTextField.becomeFirstResponder()
case lastNameTextField:
passwordTextField.becomeFirstResponder()
case passwordTextField:
// Validate Text Field
let (valid, message) = validate(textField)
if valid {
emailTextField.becomeFirstResponder()
}
// Update Password Validation Label
self.passwordValidationLabel.text = message
// Show/Hide Password Validation Label
UIView.animate(withDuration: 0.25, animations: {
self.passwordValidationLabel.isHidden = valid
})
default:
emailTextField.resignFirstResponder()
}
return true
}
}
In viewDidLoad()
, we invoke a helper method, setupView()
. In setupView()
, we hide the validation label.
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
// Setup View
setupView()
}
// MARK: - View Methods
fileprivate func setupView() {
// Configure Password Validation Label
passwordValidationLabel.isHidden = true
}
Build and run the application to test the validation we implemented. You can set the text color of the validation label to red to catch the user's eye.
Tip 5: Help the User Succeed
It's important to reiterate that small changes and improvements can make a big impact in terms of usability. The last tip I'd like to share with you involves the save button. This tip applies to almost every aspect of user interface design, not only forms.
The save button should not be enabled if the user's input is invalid or incomplete. This only confuses the user. But we don't want to hide the save button either. We need to give the user a peek of what's next. What does the user need to do if she completes the form?
To enable the save button when the form is filled out and valid, we need to observe the contents of the text fields. We do this by registering the view controller as an observer for the UITextFieldTextDidChange
notification, sent by the text fields when their text changes. In viewDidLoad()
, we register the view controller as an observer for the UITextFieldTextDidChange
notification.
// MARK: - View Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
// Setup View
setupView()
// Register View Controller as Observer
NotificationCenter.default.addObserver(self, selector: #selector(textDidChange(_:)), name: Notification.Name.UITextFieldTextDidChange, object: nil)
}
To make validating the text fields easier, we declare an outlet collection for the text fields and wire them up in Interface Builder. To enable or disable the save button, we also create an outlet for the save button.
import UIKit
class ViewController: UIViewController {
// MARK: - Properties
@IBOutlet var firstNameTextField: UITextField!
@IBOutlet var lastNameTextField: UITextField!
@IBOutlet var passwordTextField: UITextField!
@IBOutlet var emailTextField: UITextField!
@IBOutlet var saveButton: UIButton!
@IBOutlet var textFields: [UITextField]!
@IBOutlet var passwordValidationLabel: UILabel!
...
}
The implementation of the textDidChange(_:)
method is straightforward. We declare a helper variable, formIsValid
and loop over the text fields, validating each text field. From the moment we run into a text field that isn't valid, we set formIsValid
to false
and break out of the loop. The value stored in formIsValid
is used to update the save button.
// MARK: - Notification Handling
@objc private func textDidChange(_ notification: Notification) {
var formIsValid = true
for textField in textFields {
// Validate Text Field
let (valid, _) = validate(textField)
guard valid else {
formIsValid = false
break
}
}
// Update Save Button
saveButton.isEnabled = formIsValid
}
Before you run the application, we need to disable the save button in the setupView()
helper method.
// MARK: - View Methods
fileprivate func setupView() {
// Configure Save Button
saveButton.isEnabled = false
// Configure Password Validation Label
passwordValidationLabel.isHidden = true
}
Build and run the application to see what we have so far. The save button is disabled as long as the form is not valid.
More To Come
While there's much more we can do to improve the registration form we created, the tips we discussed in this tutorial are a good start. In the next tutorial, I show you how to update the user interface if the keyboard overlaps the form. That's another common problem developers face.