Greetings, traveler!
In our previous tutorial, we built a custom SwiftUI TextField with UITextField inside. Check it out if you still need to. Today, we’re diving deeper into the customization of this text field, exploring the tools at our disposal.
UITextField properties
Let’s enhance our CustomTextField by adding a new Boolean variable, isUserInteractionEnabled. When set to true, this variable allows user interaction with the text field. We’ll use this variable in our configuration function for the UITextField, enabling us to set the same property of our UITextField with this value. This configuration takes place within the updateUIView function.
func updateUIView(_ uiView: UITextField, context: Context) {
configure(uiView)
}
private func configure(_ textField: UITextField) {
textField.text = text
textField.isUserInteractionEnabled = isUserInteractionEnabled
}
But how can we set this property without getting lost in complex code? Like the last time, I suggest a convenient solution — an extension for our CustomTextField. Within this extension, we’ll declare configuration functions that can be easily called inside any SwiftUI View while using our CustomTextField. This approach simplifies our coding tasks, allowing us to focus on the core functionality.
extension CustomTextField {
func disabled(_ disabled: Bool) -> CustomTextField {
var view = self
view.isUserInteractionEnabled = !disabled
return view
}
}
Now, we can call this function as a view modifier.
struct ContentView: View {
@State private var text = ""
@State private var isTextFieldDisabled = false
var body: some View {
CustomTextField(text: $text)
.disabled(isTextFieldDisabled)
}
}
isFirstResponder
To force our text field to become a first responder, we can create a Binding property named isFirstResponder. We can also add this parameter to our initializer and use it while creating and updating the view. As you remember, we created this property earlier, but let’s repeat some points.
init(
text: Binding<String>,
isFirstResponder: Binding<Bool>
) {
self._text = text
self._isFirstResponder = isFirstResponder
}
func updateUIView(_ uiView: UITextField, context: Context) {
configure(uiView)
if isFirstResponder {
uiView.becomeFirstResponder()
} else {
uiView.resignFirstResponder()
}
}
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
if isFirstResponder {
textField.becomeFirstResponder()
}
return textField
}
Secure text entry
There are times when we need to secure our text fields, such as when we’re asking the user to enter a password. In these cases, we can hide the text inside the field. We declare a new property and set its value to our textField’s isSecureTextEntry property to achieve this. However, there are a few nuances to consider. To ensure the cursor remains in the correct position after a view update, we can read and save its position before updating the property, then set it back to the recorded value.
Another thing that we should handle is that UITextField will remove all text after toggling the property isSecureTextEntry to true. That’s why we should set our text again.
public func updateUIView(_ uiView: UITextField, context: Context) {
configure(uiView)
if isSecure != uiView.isSecureTextEntry {
var start: UITextPosition?
var end: UITextPosition?
if let selectedRange = uiView.selectedTextRange {
start = selectedRange.start
end = selectedRange.end
}
uiView.isSecureTextEntry = isSecure
if isSecure && isFirstResponder {
if let currentText = uiView.text {
uiView.text?.removeAll()
uiView.insertText(currentText)
}
}
if isFirstResponder {
if let start = start, let end = end {
uiView.selectedTextRange = uiView.textRange(from: start, to: end)
}
}
}
if isFirstResponder {
uiView.becomeFirstResponder()
} else {
uiView.resignFirstResponder()
}
}
And don’t forget to update our configuration function.
private func configure(_ textField: UITextField) {
textField.text = text
textField.isUserInteractionEnabled = isUserInteractionEnabled
textField.isSecureTextEntry = isSecureTextEntry
}
That’s it for now. Next time, we will discuss creating an inputAccessoryView for our text field. If you want to check out the complete code — here is the link to the GitHub repository. See you soon!