ViewModifier to Dismiss Keyboard
September 18, 2024 at 7:00AMI recently needed to update my CustomTextField to display a Done button whenever the current keyboard type in iOS did not. Turns out a ViewModifier helped with that. The modifier itself, an isInputActive param, and the implementation are all this is needed.
CustomTextField
Add the following to the CustomTextField
- param
@FocusState var isInputActive: Bool
- conditional TextField
[UIKeyboardType.numberPad, .phonePad].contains(keyboardType)
- TextField.focus
.focused($isInputActive)
@FocusState var isInputActive: Bool
if [UIKeyboardType.numberPad, .phonePad].contains(keyboardType) {
TextField(placeholderText, text: $input,
onEditingChanged: { editing in
isEditing = editing
})
.foregroundColor(.grey50)
.multilineTextAlignment(textFieldAlignment)
.keyboardType(keyboardType)
.padding()
.disabled( status == .disabled )
.focused($isInputActive)
} else {
TextField(placeholderText, text: $input,
onEditingChanged: { editing in
isEditing = editing
})
.foregroundColor(.grey50)
.multilineTextAlignment(textFieldAlignment)
.keyboardType(keyboardType)
.padding()
.disabled( status == .disabled )
}...
Modifier
/// View modifier for use with views that display number input without return/done keys in keyboard.
///
/// NOTE: This needs to be added in the containing Form/View instead of the TextField directly or duplicates will appear.
/// see. https://stackoverflow.com/questions/71279700/duplicate-toolbar-in-swiftui
///
struct UnitTxtFieldDoneModifier : ViewModifier {
@FocusState<Bool>.Binding var isInputActive: Bool
func body(content: Content) -> some View {
content
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
/// Only display "Done" when we have a state.
if $isInputActive.wrappedValue {
Spacer()
Button("Done") {
isInputActive = false
}
}
}
}
}
}
Modifier View Extension
extension View {
func textFieldDoneButton(isInputActive: FocusState<Bool>.Binding) -> some View {
ModifiedContent(content: self, modifier: TextFieldDoneModifier(isInputActive: isInputActive))
}
}
Lasly, be sure to update the Form/View that presents the CustomTextField. As noted in the code comment there is a bug in SwiftUI that causes duplicates if this modification is used directly on the TextField. It MUST be used on the containing form/view.
View
Add the FocusState param to view and cutsomTextField. then call the .textFieldDoneButton(...).
@FocusState var isInputActive: Bool
...
View {
VStack {
CustomTextField(status: shouldAllowEditing,
overlineText: "Phone Number",
input: $phoneNumber,
keyboardType: .phonePad,
isInputActive: _isInputActive)
.frame(height: 72)
} .textFieldDoneButton(isInputActive: $isInputActive)
}
Happy Coding ;-)