skip to Main Content

Automatic keyboard avoidance seems to work fine if it’s a regular TextField (i.e. one that doesn’t expand on an axis), whether or not it is contained in a ScrollView

Keyboard avoidance also seems to work with the new TextField(_:text:axis) introduced in iOS 16 if it’s simply placed in a VStack without being wrapped in a ScrollView. It will even continue to avoid the keyboard correctly as the height expands with more text.

But I can’t seem to get keyboard avoidance to work with TextField(_:text:axis) if it is placed inside a ScrollView

I can employ the hacky method of using a ScrollViewReader combined with DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(400)) to wrap the proxy.scrollTo() when the TextField is focused. This sort of works when you first focus the field, but I can’t seem to get the ScrollView to continue to adjust its position as the TextField expands.

Here is an example:

struct KeyboardAvoidingView: View {
    @State var text = ""
    
    var body: some View {
        ScrollViewReader { proxy in
            ScrollView {
                VStack {
                    Color.red
                        .frame(height: 400)
                    Color.blue
                        .frame(height: 400)
                    TextField("Name", text: $text, axis: .vertical)
                        .padding(.vertical)
                        .onTapGesture {
                            DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(400)) { 
                                withAnimation(.default) {
                                    proxy.scrollTo(0)
                                }
                            }
                        }
                        .onChange(of: text) { newValue in
                            proxy.scrollTo(0)   // This doesn't seem to do anything
                        }
                    Spacer()
                        .frame(height: 0)
                        .id(0)
                }
            }
        }
    }
}

I guess I’m wondering whether this is expected behavior, or a bug. And regardless if it’s one or the other, I’m wondering if I can have an auto-expanding text field inside a scroll view that I can make avoid the keyboard even as the height of the field expands?


UPDATE: It turns out, the issue was with placing the TextField inside a VStack instead of a LazyVStack. I assume ScrollView doesn’t know what to do with just a regular VStack in certain situations. If I replace the VStack with a LazyVStack in my example, everything works as expected!

2

Answers


  1. Chosen as BEST ANSWER

    I answered the question with the update posted above. The issue was with using VStack instead of LazyVStack


  2. This is a long time known bug in the TextField component, but you may achieve the desired behavior by using an anchor: .bottom in the proxy.scrollTo call of your onChange.

    it’ll look like this:

    // ...
    TextField("Name", text: $text, axis: .vertical)
        .padding(.vertical)
        .onTapGesture {
            DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(400)) { 
                withAnimation(.default) {
                    proxy.scrollTo(0)
                }
            }
        }
        .onChange(of: text) { newValue in
            // This will always scroll to the bottom of the text editor, 
            // just make sure to pass the right value in the first parameter
            // that will identify your TextEditor
            proxy.scrollTo(MyTextEditorId, anchor: .bottom)   
        }
    // ...
    

    You may need some additional work to handle the editing of upper parts of the text editor when it’s taller than your screen

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search