Home > OS >  SwiftUI Focus State API environment variable not working
SwiftUI Focus State API environment variable not working

Time:05-03

Environment value isFocused doesn't seem to work when we want to observe focus state of SwiftUI textfield. Is there any other way to do this, besides passing the value to TextFieldStyle's init (which we would have to do for every Textfield)? Doesn't work on device either.

What is the preferred way of changing Textfield's appearance when its focus state changes?

Example:

SwiftUI TextFieldStyle defined as follows:

struct MyTextFieldStyle: TextFieldStyle {
    @Environment(\.isFocused) var isFocused: Bool

    func _body(configuration: TextField<_Label>) -> some View {
        configuration
            .padding()
            .overlay(
                RoundedRectangle(
                    cornerRadius: 10.0, style: .continuous
                )
                .stroke(isFocused ? .green : .gray, lineWidth: 3)
            )
            .accentColor(Color(uiColor: .white))
    }
}

#if DEBUG
private struct TestView: View {
    @FocusState private var focusedTextfield: FocusField?

    enum FocusField: Hashable {
        case textfield1, textfield2
    }

    var body: some View {
        VStack(spacing: 16) {
            TextField("hello", text: .constant("Hi"))
                .textFieldStyle(MyTextFieldStyle())
                .focused($focusedTextfield, equals: .textfield1)

            TextField("hello", text: .constant("Hi"))
                .textFieldStyle(MyTextFieldStyle())
                .focused($focusedTextfield, equals: .textfield2)
        }.onAppear {
            focusedTextfield = .textfield1
        }
    }
}

struct MyTextfieldStyle_Previews: PreviewProvider {
    static var previews: some View {
        ZStack {
            TestView()
        }
    }
}
#endif

// EDIT: Working solution based on https://stackoverflow.com/a/72092987/7828383

struct MyTextFieldStyle: TextFieldStyle {
    @FocusState var isFocused: Bool

    func _body(configuration: TextField<_Label>) -> some View {
        configuration
            .padding()
            .focused($isFocused)
            .overlay(
                RoundedRectangle(
                    cornerRadius: 10.0, style: .continuous
                )
                .stroke(isFocused ? .green : .gray, lineWidth: 3)
            )
            .accentColor(Color(uiColor: .white))
    }
}

#if DEBUG
private struct TestView: View {
    @FocusState private var focusedTextfield: FocusField?

    enum FocusField: Hashable {
        case textfield1, textfield2
    }

    var body: some View {
        VStack(spacing: 16) {
            TextField("hello", text: .constant("Hi"))
                .textFieldStyle(MyTextFieldStyle())
                .focused($focusedTextfield, equals: .textfield1)

            TextField("hello", text: .constant("Hi"))
                .textFieldStyle(MyTextFieldStyle())
                .focused($focusedTextfield, equals: .textfield2)
        }.onAppear {
            DispatchQueue.main.async {
                focusedTextfield = .textfield1
            }
        }
    }
}

struct MyTextFieldStyle_Previews: PreviewProvider {
    static var previews: some View {
        ZStack {
            TestView()
        }
    }
}
#endif

CodePudding user response:

You have met a couple of different issues:

  1. As far as I know there is no public protocol for custom TextFieldStyles. But you can do your own TextField struct with the same behavior.
  2. In this struct you can use another local @FocusState var. I didn't get the environment var working, but this does.
  3. To set the initial focus in your main view you have to wait some time using asyncAfter
struct MyTextField: View {

    @FocusState private var isFocused: Bool

    let title: String
    @Binding var text: String
    
    init(_ title: String, text: Binding<String>) {
        self.title = title
        self._text = text
    }
    
    var body: some View {
        TextField(title, text: $text)
            .focused($isFocused) // important !
            .padding()
            .overlay(
                RoundedRectangle(
                    cornerRadius: 10.0, style: .continuous
                )
                .stroke(isFocused ? .green : .gray, lineWidth: 3)
            )
            .accentColor(Color(uiColor: .red))
    }
}


struct ContentView: View {
    
    @FocusState private var focusedTextfield: FocusField?
    
    enum FocusField: Hashable {
        case textfield1, textfield2
    }
    
    @State private var input1 = "Hi"
    @State private var input2 = "Hi2"

    var body: some View {
        VStack(spacing: 16) {
            MyTextField("hello", text: $input1)
                .focused($focusedTextfield, equals: .textfield1)
            
            MyTextField("hello", text: $input2)
                .focused($focusedTextfield, equals: .textfield2)
            
            // test for changing focus
            Button("Field 1") { focusedTextfield = .textfield1}
            Button("Field 2") { focusedTextfield = .textfield2}

        }
        .padding()
        .onAppear {
            DispatchQueue.main.asyncAfter(deadline: .now()   0.5) {
                focusedTextfield = .textfield1
            }
        }
    }
}
  • Related