Search code examples
swiftswiftuifocustextfield

Can't get TextField to switch focus after pressing next


I have a list of text views where I would like to switch the focus after the next key has been pressed. I have a struct which represents my text field data:

struct WordEntry : BaseIndexStruct, Identifiable, Equatable, Hashable{
    let id = UUID()
    var word : String = ""
    var index : Int
    
    init(index: Int) {
        self.index = index
    }
}

I then have a view for a text field where the focus is controlled, I added a print statement here to see if word.index == focus! is ever actually true. It does become true but setting the isFocused has no impact and during the print it is still false.

struct SolvedField: View {
    @State var word: WordEntry
    @FocusState var isFocused: Bool
    @Binding var focus: Int?
    var nextFocus: (Int) -> Void

    var body: some View {
        TextField("", text: $word.word)
            .onChange(of: focus, perform: { newValue in
                if(focus != nil){
                    self.isFocused = word.index == focus!
                    print("index \(word.index), focus \(focus!), isfocused: \(self.isFocused) should be focused \(word.index == focus!)")
                }
            })
            .focused(self.$isFocused)
            .submitLabel(.next)
            .onSubmit {
                isFocused = false
                self.nextFocus(word.index)
            }
    }
}

and then a view which contains a list of word entries which are then shown as the text field control.

struct SolvedLettersLine: View {
    @State var words = [WordEntry(index: 0), WordEntry(index: 1), WordEntry(index: 2), WordEntry(index: 3)]
    @State var focus : Int?
    
    var body: some View {
        VStack(spacing:10){
            Group{
                ForEach(words) { word in
                    SolvedField(word: word, focus: $focus, nextFocus:{
                        focus = $0 + 1
                    })
                }
            }
        }
    }
}

Unfortunately I am completely stuck on how to get iOS to switch textfield focus to work, does anyone have an idea?


Solution

  • You should have just one @FocusState in SolvedLettersLine, that represents which field is currently focused. This should be of type Int?.

    You can now use the focused(_:equals:) modifier to control the focus of text fields.

    Then, you can pass this @FocusState to SolvedField as a FocusState.Binding, similar to how you would pass a regular @State to a @Binding.

    struct SolvedField: View {
        // side note: word should not be a @State!
        @Binding var word: WordEntry
    
        @FocusState.Binding var focus: Int?
        let nextFocus: () -> Void
    
        var body: some View {
            TextField("", text: $word.word)
                .focused($focus, equals: word.index)
                .submitLabel(.next)
                .onSubmit {
                    nextFocus()
                }
        }
    }
    
    struct SolvedLettersLine: View {
        @State var words = [WordEntry(index: 0), WordEntry(index: 1), WordEntry(index: 2), WordEntry(index: 3)]
        @FocusState var focus: Int?
        
        var body: some View {
            VStack(spacing:10){
                ForEach($words) { $word in
                    SolvedField(word: $word, focus: $focus) { 
                        focus? += 1
                    }
                }
            }
        }
    }