I would like to create a reusable view
that embeds a TextField
and a visual indicator (underline Rectangle
) view that changes color when the TextField
is editing (i.e. has focus).
In an example container view, I may have several of these reusable views, and I'm using the FocusState
pattern like so:
private enum FocusedField {
case first
case second
case third
}
@FocusState private var focusedField: FocusedField?
MyCustomTextField(...)
.focused($focusedField, equals: .second)
In general, this works. Focus will shift to the custom text field even though it's inside a container view with the TextField
and a Rectangle(...)
. (Not really sure I understand how this is working, tbh)
However, I don't know how to generically have the Rectangle
change color based on whether or not the associated TextField
has focus.
The rectangle is just:
Rectangle()
.fill(isFocused ? Color.blue : Color.gray)
.frame(maxWidth: .infinity, minHeight: 2, maxHeight: 2)
.padding(.horizontal)
Any guidance appreciated.
CodePudding user response:
You can pass FocusState
and value to your CustomTextField
view.
here is the demo code.
struct MyCustomTextField<Value: Hashable>: View {
@Binding var text: String
var focusedField: FocusState<Value?>.Binding
var value: Value
var body: some View {
VStack {
TextField("Placeholder", text: $text)
.focused(focusedField, equals: value)
Rectangle()
.fill(focusedField.wrappedValue == value ? Color.blue : Color.gray)
.frame(maxWidth: .infinity, minHeight: 2, maxHeight: 2)
.padding(.horizontal)
}
}
}
struct ContentView: View {
@State var firstTextField: String = ""
@State var secondTextField: String = ""
@State var thirdTextField: String = ""
@FocusState private var focusedField: FocusedField?
var body: some View {
VStack {
MyCustomTextField(text: $firstTextField, focusedField: $focusedField, value: .first)
MyCustomTextField(text: $secondTextField, focusedField: $focusedField, value: .second)
MyCustomTextField(text: $thirdTextField, focusedField: $focusedField, value: .third)
Button("Change") {
switch focusedField {
case .first:
focusedField = .second
case .second:
focusedField = .third
default:
focusedField = .first
}
}
}
}
}
CodePudding user response:
Just use internally own FocusState
, it does not conflict with external
struct MyCustomTextField: View {
@Binding var text: String
@FocusState var isFocused // << internal
var body: some View {
TextField("", text: $text)
.focused($isFocused) // << get state
.padding()
.background(
Rectangle()
.fill(isFocused ? Color.blue : Color.gray) // !!
.frame(maxWidth: .infinity, minHeight: 2, maxHeight: 2)
.padding(.horizontal)
, alignment: .bottom)
}
}
Tested with Xcode 13.4 / iOS 15.5