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 TextField
s 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 TextField
s 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 aUITextField
.It has a
Coordinator
that implementsUITextFieldDelegate
.The
Coordinator
is handling thetextFieldShouldReturn
message. It scans the surroundingUIView
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.