SwiftUI and the Responder Chain

While I love working with SwiftUI, there is a piece missing which I hoped Apple would address with SwiftUI 2 - the responder chain. Or more specifically, responders with respect to keyboard input. If you are unfamiliar with the concept of a responder, it’s simply how apps receive and handle events. In other words, how app components respond to events they receive. Tapping on a key in the virtual keyboard (for iOS) or a physical keyboard (for macOS) generates an event. That event must find its way to an object that will handle - or respond - to it.

The SwiftUI TextField is woefully bereft of the ability to be assigned responder responsibility. It’s not that TextField cannot accept input from the keyboard. It can and works just great. But what if you have a set of TextFields such as login and password field. Normally you would tap on the login field (which causes it to become a responder and the keyboard appears), type in the user name, then press the “return” key - which is most often labeled “next” in this circumstance - and the caret jumps to the next field.

That is still not possible with SwiftUI. If you place two TextFields on a screen, enter text into one, press “return”, nothing happens. With TextField you can receive information once the “return” key is pressed and you can programmatically dismiss the keyboard. But you cannot get the current TextField to resign being the responder and get the next field to become the responder. Just isn’t going to happen.

The solution is still to write a custom component using UIViewRepresentable. That’s not too hard and I’ll show you one way in a moment. What I do not understand is why Apple has not addressed this. I can understand not providing some automatic input field chaser, but to not provide the tools to do it yourself does not seem good form. You could argue that using UIViewRepresentable is their solution, but given what Apple have done with SwiftUI 2, seems a shame.

GitHub

I have a public GitHub repo with some code you can use and build upon. Its nothing fancy and you can muck with it as you like. It basically works like this:

  • It is a UIViewRepresentable that makes a UITextField.

  • It has a Coordinator that implements UITextFieldDelegate.

  • The Coordinator is handling the textFieldShouldReturn message. It scans the surrounding UIView hierarchy looking for the next field as specified by the current component. If it finds one, that becomes the new responder.

  • If there is no next field, the current component resigns as the responder, effectively removing the keyboard.

To make this work, the component (cleverly called TextFieldResponder) assigns each underlying UITextField a unique tag, incrementing the tag numbers. The default tag is 100 for the first one, 101 for the second, and so forth. When the textFieldShouldReturn function is called, it checks the given textField for its tag, increments it, then finds a UIView with that tag and makes it the responder.

You use it like this:

TextFieldResponder(title: “Login”, text: self.$login)
TextFieldResponder(title: “Password”, text: self.$password)
.isSecure(true)

As with SwiftUI TextField, the title becomes the placeholder and the text is a binding that gets changed as the user types. There is also a trailing closure you can implement which is called when the “return” key is tapped.

You can find my GitHub repository at: peterent/TextFieldResponder

Modifiers

You can see in the above example, the Password field is being made secure by the presence of the .isSecure(true) modifier. This is not actually a real modifier as UIViewRepresentable does not support SwiftUI modifiers. This is really an extension function that returns self. There are few more I added:

  • keyboardType to change which keyboard to use for the field;

  • returnKeyType to change the appearance of the “return” key;

  • autocapitalization to change how capitalization works;

  • autocorrection to turn auto correct on or off.

Just use them as you would any modifier.

TextFieldResponder(title: "Login", text: self.$login)
.keyboardType(.emailAddress)

.autocorrection(.yes)
.autocapitalization(.none)

Final Thoughts

This is not a perfect solution, but if you have a bunch of input fields in your app and you would like the user to quickly move between them without having to leave the keyboard and go tap on this, this is a way to do it.

Previous
Previous

Weather Demo for SwiftUI

Next
Next

SwiftUI 2.0 - First Days