Skip to content →

Text Field Advancing Protocol

In Keyboard Responsive View Controller, I discussed a demo project that presented a UIViewController extension for automatically moving the active text field into view when obscured by the keyboard. Not discussed there was functionality included in that project for advancing the cursor to the next text field when the user taps the Return key. I discuss it now.

By default, when the user taps the Return key on the iOS keyboard (in this project configured as a Next or Done button), nothing happens. By conforming your UIViewController to the TextFieldAdvancing protocol, your controller gains a text field auto-advance functionality. As a result, when the user taps Return, the focus automatically moves to the next field in the sequence. Optionally, if it’s the last field on the view, it will instead segue to a new scene.

This protocol does require your controller be registered as a delegate of each text field, which is easiest done via the storyboard. Just control-drag from the text field to the view controller button at the top of the scene, and select the “delegate” outlet. You can also do this programmatically with myTextField.delegate = self (this functionality is included in the Keyboard Responsive View Controller extension).

@objc protocol TextFieldAdvancing: UITextFieldDelegate {
  var textFieldSegueIdentifier: String? { get }
}

extension UIViewController {

  /// In response to the user tapping the Return key in a text field, calls upon `advanceFirstResponder(from:)`.
  func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    advanceFirstResponder(from: textField)
    return true
  }

  /// Advances first responder to the next text field in the `tag` sequence. For the last text field in the sequence, advances to the next scene via the segue given by `textFieldSegueIdentifier`.
  func advanceFirstResponder(from textField: UITextField) {
    guard let vc = self as? TextFieldAdvancing else { return }
    if let nextTextField = view.viewWithTag(textField.tag + 1) {
      nextTextField.becomeFirstResponder()
    } else {
      if let textFieldSegueIdentifier = vc.textFieldSegueIdentifier {
        performSegue(withIdentifier: textFieldSegueIdentifier, sender: nil)
      }
    }
  }
}

The @objc designation on line 1 is required in order for the Swift protocol’s UITextFieldDelegate method textFieldShouldReturn(_:) to be seen and called by the Objective-C UITextField. Without that, tapping Return has no effect.

Turning to the extension, in a perfect Swift world, this would be a protocol extension, with line 5 reading:

extension TextFieldAdvancing where Self: UIViewController {

However, the UITextFieldDelegate calls are similarly never made.  While it runs, the compiler warns:

Non-@objc method textFieldShouldReturn does not satisfy optional requirement of @objc protocol UITextFieldDelegate.

Matthew Seaman provides a good discussion of this problem, summing up the issue so:

@objc functions may not currently be in protocol extensions. You could create a base class instead, though that’s not an ideal solution.

No, not ideal. Using a base class effectively eliminates the utility of using a protocol in the first place. And because we can’t designate that this extension is of TextFieldAdvancing, we must explicitly test for conformance on line 15.

Let that not detract from the fact, however, that this protocol and extension do work. If you want the user to be able to advance automatically through text fields managed by your view controller, simply conform to TextFieldAdvancing, like so:

class MyViewController: UIViewController, TextFieldAdvancing {
  var textFieldSegueIdentifier: String? = "mySegue"
}

And don’t forget to assign the controller as the delegate of each text field!

Published in Swift Language User Interface

Comments

Leave a Reply

Your email address will not be published.