swiftauthenticationswiftuitextfieldpin-code

SwiftUI: How to move/jump to next TextField without hiding keyboard?


I'm solving how to jump to the next TextField in SwiftUI. Below is example code which is not working correctly, but there is an idea of how it could work.

struct ContentView: View {
    @FocusState private var focused: Bool
    @State private var activeIndex: Int = 0

    var body: some View {
        HStack(spacing: 3) {
            ForEach(0..<10, id: \.self) { index in
                Stone(activeIndex: $activeIndex, focused: _focused, index: index)
            }
        }
        .padding(.horizontal)
    }
}

The problem when I'm trying to jump to next TextField is, that nothing will happen.

struct Stone: View {
    @Binding var activeIndex: Int
    @FocusState var focused: Bool
    @State var index: Int

    @State private var text: String = ""

    var body: some View {
        GeometryReader { bounds in
            ZStack {
                TextField("", text: $text)
                    .multilineTextAlignment(.center)
                    .focused($focused)
                    .background(index == activeIndex ? .red : .green)
                    .onChange(of: text) { newValue in
                        if let lastValue = newValue.last {
                            text = String(lastValue)

                            activeIndex += 1

                            focused = true
                        }
                    }
            }
            .frame(width: bounds.size.width, height: bounds.size.height)
        }
        .scaledToFit()
    }
}

I found some number of ways how to jump to next TextField, but mostly in UIKit. So my question is, how can I jump to next TextField in SwiftUI?


Solution

  • Here is an imperfect version example to understand what I meant by my question.

    struct ContentView: View {
        @FocusState private var focusedField: Int?
    
        var body: some View {
            HStack(spacing: 3) {
                ForEach(0..<10, id: \.self) { index in
                    Stone(focusedField: _focusedField, index: index)
                }
            }
            .padding(.horizontal)
        }
    }
    
    struct Stone: View {
        @FocusState var focusedField: Int?
        @State var index: Int
    
        @State private var text: String = ""
    
        var body: some View {
            TextField("", text: $text)
                .focused($focusedField, equals: index)
                .textFieldStyle(.roundedBorder)
                .onChange(of: text) { _ in
                    setFocus(index: index)
                }
        }
    
        func setFocus(index: Int) {
            // Default to the first box when focus is not set or the user reaches the last box
            if focusedField == nil || focusedField == 9 {
                focusedField = 0
            } else {
                // Another safety check for the index
                if index == 9 {
                    focusedField = 0
                } else {
                    focusedField = index + 1
                }
            }
        }
    }