I have a TextField
somewhere deep in the hierarchy, and a Button
that need to dismiss the TextField
's keyboard upon tapping, the example I found so far are:
FocusState
and set it to dismiss keyboard in button's actionUIApplication.shared.sendActio(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
in button actionIf I prefer method 1, do I have to pass a focused
state variable all the way to the TextField
, or is there a better approach? Thanks!
If you don't want to pass the focus state binding through all those intermediate views, an alternative would be to pass the focus information with environment
(from parent to child) and preference
(from child to parent).
This is quite a lot of boilerplate, and might be more effort than passing the focus state binding, depending on how deeply nested the text fields are.
Suppose we have some text fields that we want to control focus, that is nested in a view called Nested
.
enum FocusedTextField {
case one, two
}
struct Nested: View {
var body: some View {
TextFields()
}
}
struct TextFields: View {
@State var text1 = "Foo"
@State var text2 = "Bar"
@FocusState var focus: FocusedTextField?
var body: some View {
Group {
TextField("One", text: $text1)
.focused($focus, equals: .one)
TextField("Two", text: $text2)
.focused($focus, equals: .two)
}
}
}
We can now add a FocusKey
that is both an environment key and a preference key:
struct FocusKey: EnvironmentKey, PreferenceKey {
static func reduce(value: inout FocusedTextField?, nextValue: () -> FocusedTextField?) {
// "??", because we only want to find the focused view across the sibling views
value = nextValue() ?? value
}
static let defaultValue: FocusedTextField? = nil
}
extension EnvironmentValues {
var focus: FocusedTextField? {
get { self[FocusKey.self] }
set { self[FocusKey.self] = newValue }
}
}
Now in TextFields
, we can add the \.focus
environment, set focus
when the environment changes, and also set the preference to focus.
@Environment(\.focus) var focusFromEnvironment
// ...
Group {
...
}
.onChange(of: focusFromEnvironment) { _, new in
focus = new
}
.preference(key: FocusKey.self, value: focus)
Now, in the root view, we can have an unfocus button like this:
struct ContentView: View {
@State var focus: FocusedTextField? = nil
var body: some View {
Nested()
.environment(\.focus, focus)
.onPreferenceChange(FocusKey.self) { new in
// when something is focused, this gets called
focus = new
}
Button("Unfocus") {
// setting this changes the environment
focus = nil
}
}
}