Home > Software engineering >  Focused TextField ends up covered by the keyboard in SwiftUI
Focused TextField ends up covered by the keyboard in SwiftUI

Time:04-27

What I'm trying to accomplish

A vertical list of TextField views that user enters values in one at a time. After entering a value in a text field and pressing keyboard submit button the focus moves to the next text field.

The problem I'm facing

The keyboard obstructs the active text field. I was hoping that if I used ScrollView it would automagically scroll to the next focused text field, but that's not the case.

Then I decided to try using ScrollViewReader's ScrollViewProxy object to scroll to the focused text field, which works! But not quite. It scrolls to the selected text field, but not enough to go over the keyboard. It seems to always be just a little bit underneath the top of the keyboard (regardless of whether there's keyboard toolbar or autocorrection bar or nothing).

Here's a screenshot of roughly where the text field ends up after I call proxy.scrollTo(focusedInput):

Here's where I would want it to end up:

Minimal code sample

struct ContentView: View {
    @State var inputsValues: [String]
    @FocusState var focusedInput: Int?
    
    init() {
        inputsValues = (0..<30).map { _ in "" }
    }
    
    var body: some View {
        ScrollViewReader { proxy in
            ScrollView {
                VStack {
                    ForEach((0..<inputsValues.count), id: \.self) { i in
                        TextField("Value", text: $inputsValues[i])
                            .focused($focusedInput, equals: i)
                            .submitLabel(.next)
                            .id(i)
                            .onSubmit {
                                if (i   1) < inputsValues.count {
                                    focusedInput = i   1
                                    proxy.scrollTo(focusedInput)
                                } else {
                                    focusedInput = nil
                                }
                            }
                    }
                }
            }
        }.toolbar {
            ToolbarItem(placement: .keyboard) {
                Text("This is toolbar")
            }
        }
    }
}

CodePudding user response:

Chaning two states in one closure (when they are related somehow or affects one area) are handled not very good (or not reliable). The fix is to separate them in different handlers.

Tested with Xcode 13.3 / iOS 15.4

demo

Here is main part:

VStack {
   // ... other code

            .onSubmit {
                // update state here !!
                if (i   1) < inputsValues.count {
                    focusedInput = i   1
                } else {
                    focusedInput = nil
                }
            }
    }
}
.onChange(of: focusedInput) {
    // react on state change here !!
    proxy.scrollTo($0)
}

Complete test module is here

  • Related