Passing down @FocusState to another view
Asked Answered
R

3

30

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?

Rattletrap answered 28/11, 2021 at 6:40 Comment(1)
For me it's working without passing down the state. It's inheriting automaticallyBiller
I
28

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)
        }

    }
  }
}
Intensify answered 28/11, 2021 at 7:4 Comment(8)
I was wondering how would I be able to to set the focusedField to nil when a button is clicked on view2. It says its immutable in the View2 and was just wandering how would I be able to do it.Rattletrap
To set the focusedField in View2, wouldn't you change the focused.wrappedValue ?Genesis
This works but how do I make the preview work with such a weird variable? var focused: FocusState<View1.Field?>.Binding ?Ba
@Ba - did you find a way to use such variable in previews .. ? if yes, could you please share ?Antecedent
@Ba Add @FocusState static var focus: Field? to the preview and inject as a binding using $Properly
Use "FocusState<Bool>().projectedValue" (reference: #75431071)Mistreat
How would pass the FocusState binding to a public init of View2? with a default value?Galley
Feels pretty strange to me, that View2 has to know about View1.Field. What if you wanted to reuse View2 somewhere else? Haven't found a great way of dealing with this besides fairly complex Generic Hashable structures.Reel
P
29

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)
    }
}
Premonition answered 8/9, 2023 at 6:38 Comment(5)
This seems a simpler way to declare the binding in the child view than the way it is done in the accepted answer. However, if the child view has an init function then the syntax from the accepted answer is needed for the parameter.Zettazeugma
Are you sure this statement is correct? If you make the parameter of type Binding<Foo> instead of maybe using just Foo, I'm pretty sure you can pass it in as normal without that other syntax. I pass bindings through my own initializers this way all the time.Bats
I like this one better since you can mutate the value in the child view.Epiphenomenalism
This is nicer, too, because if you want to also reference the isFocused value or set it, you dont have to use a bunch of .wrappedValueSwingeing
Better answer that doesn't require .wrappedValue everywhereReich
I
28

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)
        }

    }
  }
}
Intensify answered 28/11, 2021 at 7:4 Comment(8)
I was wondering how would I be able to to set the focusedField to nil when a button is clicked on view2. It says its immutable in the View2 and was just wandering how would I be able to do it.Rattletrap
To set the focusedField in View2, wouldn't you change the focused.wrappedValue ?Genesis
This works but how do I make the preview work with such a weird variable? var focused: FocusState<View1.Field?>.Binding ?Ba
@Ba - did you find a way to use such variable in previews .. ? if yes, could you please share ?Antecedent
@Ba Add @FocusState static var focus: Field? to the preview and inject as a binding using $Properly
Use "FocusState<Bool>().projectedValue" (reference: #75431071)Mistreat
How would pass the FocusState binding to a public init of View2? with a default value?Galley
Feels pretty strange to me, that View2 has to know about View1.Field. What if you wanted to reuse View2 somewhere else? Haven't found a great way of dealing with this besides fairly complex Generic Hashable structures.Reel
B
0

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)
    }
}
Bibliopole answered 21/5, 2024 at 18:24 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.