From a UX point, the goal is to commit the input when people click anywhere outside the TextField
.
For example, if the input is for renaming an item, then when clicked outside, we store the input as the new item name, and replace the TextField
with a Text
to show the new item name.
I assume it is an expected standard behavior, but please let me know if it is against Apple's MacOS standards.
Is there a standard / conventional way to achieve it with SwiftUI, or with some AppKit workaround?
As I see, onCommit
is only triggered when we hit the Return key, not when we click outside the TextField
. So what I think what I need to figure out is how to detect clicking outside.
What I considered:
- Some built-in view modifier on the
TextField
which activates this behavior, but I couldn't find any. - Detect focus loss or
editingChanged
of theTextField
, but by default, when we click on the background / a button / a text, theTextField
doesn't lose focus, nor is theeditingChanged
event triggered. - If the
TextField
is focused, add an overlay on the ContentView with anonTapGesture
modifier, but then it would take two taps to trigger a button when a TextField is focused. - Add an
onTapGesture
modifier on the ContentView, which calls afocusOut
method, but it doesn't receive the event when people tap on a child view that also has anonTapGesture
on it. - Improving on 4, also call
focusOut
from theonTapGesture
callback of all child views. So far this is the only viable option I see, but I'm not sure it is a good pattern to put extra code in allonTapGesture
s, just in order to customizeTextField
behavior.
Example code:
import SwiftUI
@main
struct app: App {
@State private var inputText: String = ""
var body: some Scene {
WindowGroup {
ZStack {
Color.secondary.onTapGesture { print("Background tapped") }
VStack {
Text("Some label")
Button("Some button") { print("Button clicked") }
TextField(
"Item rename input",
text: $inputText,
onCommit: { print("Item rename commit") }
)
}
}
}
}
}