Reputation: 1604
I was wondering how would I be able to pass down a @FocusState
to another view. Here is some example code:
struct View1: View {
enum Field {
case username, password
}
@State var passwordText: String = ""
@FocusState var focusedField: Field?
var body: some View {
// How would I be able to pass the focusedField here?
View2(text: $passwordText, placeholder: "Password")
//TextField("Password", text: $passwordText)
//.frame(minHeight: 44)
//.padding(.leading, 8)
//.focused($focusedField, equals: .password)
// How would I be able to add the commented code above to View2?
}
}
struct View2: View {
@Binding var text: String
let placeholder: String
var body: some View {
HStack {
TextField(placeholder, text: $text)
.frame(minHeight: 44)
.padding(.leading, 8)
// How would I be able to add this
//.focused(binding: , equals: )
if text.count > 0 {
Image(systemName: "xmark.circle.fill")
.font(.headline)
.foregroundColor(.secondary)
.padding(.trailing, 8)
}
}
}
}
How would I be able to pass it down to View2
?
Or is there a better way to reuse a custom text field?
Upvotes: 35
Views: 12044
Reputation: 4226
It looks like no one (including me) was aware of this:
struct ParentView: View {
@State private var text: String = ""
@FocusState private var focusedField: FocusedField?
enum FocusedField {
case textField
}
var body: some View {
MyCustomTextField(text: $text)
.focused($focusedField, equals: .textField)
}
}
struct MyCustomTextField: View {
@Binding var text: String
@FocusState var focused: Bool
var body: some View {
TextField("Placeholder", text: $text)
.focused($focused)
}
}
Basically the focus state is somehow propagated to the child view, so we don't really need to pass it manually; this is not only easier to implement but way more flexible.
I'm not sure if this was not working before (i.e on iOS 15-16) but I tried on iOS 17 simulator and iOS 18 device and it works.
Upvotes: 4
Reputation: 18194
This is how it can be done with a generic type.
struct ParentView: View {
@State private var text: String = ""
@FocusState private var focusedField: FocusedField?
enum FocusedField {
case textField
}
var body: some View {
ChildView(text: $text, focused: $focusedField, equals: .textField)
}
}
struct ChildView<Value: Hashable>: View {
@Binding var text: String
@FocusState.Binding var focused: Value?
let equals: Value?
var body: some View {
TextField("Placeholder", text: $text)
.focused($focused, equals: equals)
}
}
Upvotes: 4
Reputation: 1809
Storing FocusState<Value>.Binding
does not seem to work for me
I got it working like this, which seems to behave just like how regular Binding works:
struct ParentView: View {
@State var text: String = ""
@FocusState var isFocused: Bool
var body: some View {
ChildView(text: $text, isFocused: $isFocused)
}
}
struct ChildView: View {
@Binding var text: String
@FocusState.Binding var isFocused: Bool
var body: some View {
TextField($text)
.focused($isFocused)
}
}
Upvotes: 40
Reputation: 257543
You can pass its binding as argument, like
struct View1: View {
enum Field {
case username, password
}
@State var passwordText: String = ""
@FocusState var focusedField: Field?
var body: some View {
View2(text: $passwordText, placeholder: "Password", focused: $focusedField)
}
}
struct View2: View {
@Binding var text: String
let placeholder: String
var focused: FocusState<View1.Field?>.Binding // << here !!
var body: some View {
HStack {
TextField(placeholder, text: $text)
.frame(minHeight: 44)
.padding(.leading, 8)
.focused(focused, equals: .password) // << here !!
if text.count > 0 {
Image(systemName: "xmark.circle.fill")
.font(.headline)
.foregroundColor(.secondary)
.padding(.trailing, 8)
}
}
}
}
Upvotes: 28