Set TextField Keyboard Focus In SwiftUI

Daily Coding Tip 012

When yesterday’s Daily Coding Tip used UIViewRepresentable, I didn’t realise this would be such a controversial idea.

SwiftUI is still less than 2 years old, and there are many situations where there is no way to do the same thing without relying on UIKit. You may also notice that you get UIKit errors for many of the provided SwiftUI types, because Apple is clearly using UIViewRepresentable themselves to build a lot of the UI elements we think of as native to SwiftUI. For example List may end up giving you errors related to UITableView, even though you were so careful to banish UIKit from being used anywhere in your app.

Well, I’ve learnt my lesson.

I wonder what topic I could write about that would be less controversial?

It’s your lucky day!

We’re going to make a custom version of TextField that can control whether the keyboard is active. In other words, we’re going to control what the first responder is. First we need a Coordinator class that will be helpful in responding to events related to UITextView. Since structures can only conform to protocols and cannot inherit from classes, we are using a class for this.

import UIKit
import SwiftUI
class CustomTextFieldCoordinator: NSObject, UITextFieldDelegate {
@Binding var text: String
@Binding var isResponder: Bool
init(text: Binding<String>, isResponder : Binding<Bool>) {
self._text = text
self._isResponder = isResponder
}
func setResponder(_ isResponder: Bool) {
DispatchQueue.main.async { self.isResponder = isResponder }
}
func textFieldDidChangeSelection(_ textField: UITextField) { text = textField.text ?? "" }
func textFieldDidBeginEditing(_ textField: UITextField) { setResponder(true) }
func textFieldDidEndEditing(_ textField: UITextField) { setResponder(false) }
}

Now I have a tendency to use extensions of the provided system types that give me convenience initialisers that simplify their creation.

I’m also including a function for conditionally setting the first responder, as UITextField uses void methods for both of these and can’t just be sent a boolean value as a parameter.

import UIKit
extension UITextField {
convenience init(coordinator: CustomTextField.Coordinator) {
self.init(frame: .zero)
self.delegate = coordinator
}
func setFirstResponder(_ isResponder: Bool) {
DispatchQueue.main.async {
if isResponder {
self.becomeFirstResponder()
} else {
self.resignFirstResponder()
}
}
}
}

Finally we need to create our UIViewRepresentable. As you can see, the UITextField is created with our convenience initialiser, which sets the delegate to our CustomTextFieldCoordinator class. When the UIView is updated, we make sure that our local bindings are still up to date, including setting the first responder.

import SwiftUI
struct CustomTextField: UIViewRepresentable {
@Binding var text: String
@Binding var isResponder: Bool
func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> UITextField {
return UITextField(coordinator: context.coordinator)
}
func makeCoordinator() -> CustomTextFieldCoordinator {
return CustomTextFieldCoordinator(text: $text, isResponder: $isResponder)
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomTextField>) {
uiView.text = text
uiView.setFirstResponder(isResponder)
}
}

To give focus to the keyboard anywhere in your code, simply change the value of the isResponder binding.

Credit for the original goes to Stack Overflow user Anshuman Singh, who created a more complex version that allows you to set the next responder and choose whether the CustomTextField is a password field or not.

Oh well, at least I tried.


Get more Daily Coding Tips in your inbox!