Home > front end >  Detect `FocusState` changes in reusable TextField composite views
Detect `FocusState` changes in reusable TextField composite views

Time:06-17

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

enter image description here

CodePudding user response:

Just use internally own FocusState, it does not conflict with external

demo

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

Test module on GitHub

  • Related