Skip to content

Instantly share code, notes, and snippets.

@TheWorstProgrammerEver
Created March 5, 2022 08:22
Show Gist options
  • Save TheWorstProgrammerEver/d78dc10ec74eb0d75eea6640cf56ccf0 to your computer and use it in GitHub Desktop.
Save TheWorstProgrammerEver/d78dc10ec74eb0d75eea6640cf56ccf0 to your computer and use it in GitHub Desktop.
SwiftUI keyboard dismissal / focus lost handler.
import SwiftUI
struct Example : View {
@State private var first: String = ""
@State private var second: String = ""
var body: some View {
Form {
TextField("First", text: $first)
.onFocusLost { /* save value, etc */ }
TextEditor(text: $second)
.onFocusLost { /* save value, etc */ }
}
.keyboardDismissalEnabled()
}
}
import SwiftUI
// Sometimes you want to do something whenever the user has finished inputting text into a text field / text editor.
// A TextField has the onCommit modifier which is triggered when the user presses the "Return" key.
// A TextEditor also has this modifier but it is not triggered by pressing the "Return" key.
// Most users simply expect that upon dismissing the keyboard the value will be saved.
// However, we can't rely on onCommit because:
// 1. It is not consistent between fields, and
// 2. It is not invoked if the user does not explicitly press the "Return" key.
// It may also not be performant to save on every value update.
// In lieu of a debounce or throttle (with Combine) a more UX-centric approach is required.
// This approach lets you respond to the event where a user
// "has finished inputting text and (via whatever means) dismisses the keyboard".
extension View {
// Enables a Keyboard toolbar button to dismiss the keyboard. Put this on a root element (like a Form).
// Ensure to only add one per hierarchy.
func keyboardDismissalEnabled() -> some View {
modifier(HandlesKeyboardDismissalModifier())
}
// Registers an action callback to be invoked when focus is lost on the text field
// (whether by keyboard dismissal or otherwise).
func onFocusLost(_ action: @escaping () -> Void) -> some View {
modifier(OnFocusLostModifier(action: action))
}
}
struct OnFocusLostModifier : ViewModifier {
var action: () -> Void
@FocusState private var focus: Bool
func body(content: Content) -> some View {
content
.focused($focus)
.onChange(of: focus) { v in
if !v { action() }
}
}
}
struct HandlesKeyboardDismissalModifier : ViewModifier {
func body(content: Content) -> some View {
content
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
HStack {
Spacer()
Button(action: hideKeyboard) {
Text("Done")
}
}
}
}
}
}
fileprivate func hideKeyboard() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment