Dismissing the keyboard in iOS app

When you just want to display text, use a label. When you need the user to input text, then you need to use a text field. The key to using a text field is that your app must display a virtual keyboard on the screen. When using the virtual keyboard, you need to be aware of its position on the screen and the text fields.

If text fields appear near the top of the screen, then the virtual keyboard can slide up without risk of covering up the text fields. However, if the text fields appear too close to the bottom of the screen, the virtual keyboard will slide up and cover the text fields that the user wants to type in.

If this occurs, your app needs to slide its view up to keep the text field visible while allowing the virtual keyboard to fill the bottom of the screen. After the user gets done typing text in a text field, then the virtual keyboard needs to disappear and the text field needs to slide back down again.

Dismissing the keyboard by resigning the first responder

When a text field or text view becomes first responder, the keyboard automatically appears. Conversely, when they’re no longer the first responder, the keyboard automatically disappears. If you want to dismiss the keyboard, you need the relevant text field or text view to no longer be first responder, and you can do that by calling their resignFirstResponder method:

textField.resignFirstResponder()

Detecting the return key tap to dismiss the keyboard

You can detect that the Return key was tapped on a text field by using the delegation pattern. The text field’s delegate has a textFieldShouldReturn method that's called when the Return key is tapped, that has a reference to the text field itself. Follow the three steps for the delegation pattern:

  1. Set the view controller as the delegate for the title text field. Control-drag from the text field to the view controller.
  2. Select Delegate in the Outlets section of the context-sensitive menu that pops up.
  3. Adopt the UITextFieldDelegate protocol on an extension of the ViewController.

Implement any required methods on the protocol. You want to implement the textFieldShouldReturn method:

extension MyViewController: UITextFieldDelegate {
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return true 
    }
}

Detecting touches to dismiss the keyboard

You can detect touches using the touchesEnded method. You can use this method to dismiss the keyboard when the user touches anywhere on the screen.

To resign the first responder, instead of calling resignFirstResponder directly on the view, you can also use the more generic method endEditing on the root view of the scene. The endEditing method goes on a hunt through its subview hierarchy until it finds the first responder, and then it asks it politely to resign. If its force parameter is set to true, it’s slightly less polite with the first responder, forcing it to resign.

Add the endEditing method to a touchesEnded method:

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { 
    super.touchesEnded(touches, with: event)
    view.endEditing(true)
}

Slide the view up by the size of the virtual keyboard’s height

To make the screen slide up and down to accommodate the appearance of the virtual keyboard, every app needs to use the iOS notification center, which detects when events will occur. When the user clicks in a text field, this sends a notification that the virtual keyboard needs to appear. When the user is done editing text in a text field, this sends another notification that the virtual keyboard needs to go away.

To receive notifications, an app needs to use the addObserver method that defines an object (the view) to receive notifications along with a method to run when it receives that specific notification. To detect when the virtual keyboard should appear and disappear, we’ll need to detect two notifications:

  • keyboardWillShowNotification
  • keyboardWillHideNotification

Next, we’ll need to write two functions to display the virtual keyboard and hide it. To show the virtual keyboard, we need to slide the view up by the size of the virtual keyboard’s height, which varies depending on the type of iOS device it runs on.

To hide the virtual keyboard, we need to return the view back to its original Y position that determines its vertical position on the screen. However, the virtual keyboard will never go away as long as the cursor appears in a text field.

To tell our app to make the virtual keyboard go away, we also need to detect if the user taps anywhere outside of a text field.

To see how to make the virtual keyboard appear and disappear with text fields, follow these steps.

Click the Main.storyboard file in the Navigator pane. Xcode displays the single view. Click the Library icon to display the Object Library. Drag and drop two text fields in the bottom one-third of the view, so when the virtual keyboard slides up from the bottom, it will cover the text fields

The entire ViewController.swift file should look like this:

import UIKit
class ViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad() 
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), 
        name: UIResponder. keyboardWillShowNotification, object: nil) 
        NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide), 
        name: UIResponder. keyboardWillHideNotification, object: nil)

        let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, 
            action: #selector(self.dismissKeyboard)) 
        view.addGestureRecognizer(tap)
    }

    @objc func dismissKeyboard() { 
        view.endEditing(true)
    }

    @objc func keyboardWillShow(notification: NSNotification) {
        if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] 
            as? NSValue)?.cgRectValue {
            if self.view.frame.origin.y == 0 { 
                self.view.frame.origin.y -= keyboardSize.height
            } 
        }
    }

    @objc func keyboardWillHide(notification: NSNotification) { 
        if ((notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] 
            as? NSValue)?.cgRectValue) != nil {
            if self.view.frame.origin.y != 0 { 
                self.view.frame.origin.y = 0
            } 
        }
    } 
}

Scroll view using ScrollView

Sometimes, the content that you want on a view doesn’t fit in the view, such as when the keyboard appears over the top of the form. Scroll views make it possible for the user to scroll around a view to explore its content.

Scroll views have advantages related to managing the keyboard:

  1. They have a built-in mechanism for dismissing the keyboard.
  2. Scroll views automatically move their content so that a text field currently being edited is visible.
  3. You can call the scroll view instance method scrollRectToVisible to request that a specific area of content be visible.
  4. Allows unlimited space to add fields in the future.

Follow the steps to set up the form to use scroll views:

  1. Select your group of views or outer Stack View in the storyboard, and select Editor > Embed in > ScrollView.
  2. Pin the four edges of the scroll view to the root view. This defines the area of the scroll view.
  3. Pin the four edges of the outer stack view to the scroll view. This defines the edges of the scrollable content.
  4. Next, you’ll need to define the width and height of the scroll view’s scrollable content. Because the width and height of the scrollable content will be the width and height of the scroll view, you should indicate this in constraints.
  5. Select the scroll view, and open the Attributes Inspector. In the Keyboard property, select Dismiss Interactively.

If you run your app now, you’ll notice that the form still doesn’t scroll, even if you select a text field, causing the keyboard to appear. Because the scrollable content is the same size as the scroll view, scrolling isn’t necessary, and the keyboard showing doesn’t automatically make any adjustments to the scrollable area - you need to do this part manually.

The best approach to make this adjustment is to create a bottom margin for the scrollable content with the scroll view’s contentInset property.

You’ll need to determine the amount to offset the scrollable content. The keyboard height itself doesn’t change as it shows, so you’ll need to calculate this by subtracting the keyboard y position from the height of the root view.

Add the following to the keyboardFrameChanges method:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    NotificationCenter.default.addObserver(self, selector: #selector(keyboardFrameChanges), 
    name: UIResponder.keyboardWillChangeFrameNotification, object: nil) 
}

override func viewDidDisappear(_ animated: Bool) {
    super.viewDidDisappear(animated)
    NotificationCenter.default.removeObserver(self)
}

@objc func keyboardFrameChanges(notification:Notification) {
    //get keyboard height
    guard let userInfo = notification.userInfo,
        var keyboardFrame = (userInfo[UIResponder.keyboardFrameEndUserInfoKey]
            as? NSValue)?.cgRectValue
        else { return }
    keyboardFrame = self.view.convert(keyboardFrame, from: nil)

    let offset = self.view.frame.height - keyboardFrame.origin.y

    scrollView.contentInset.bottom = offset
    scrollView.scrollIndicatorInsets.bottom = offset  
}

Create an outlet for the scroll view and call it scrollView.

You can now set the contentInset property on the scroll view:

scrollView.contentInset.bottom = offset

Run the app, tap on a text field, and you should find that your form is now scrollable! One thing will appear a little strange though—the scroll indicator on the right isn’t right. Set a bottom margin for the scroll indicator as well to resolve this:

scrollView.scrollIndicatorInsets.bottom = offset