skip to Main Content

I have a layout like below, with List and TextField in a VStack (chat-like interface).

struct ContentView: View {
    @State var input: String = ""
    
    var body: some View {
        VStack(alignment: .center) {
            List ((1...40), id: .self) { index in
                row(forIndex: index)
                    .padding(.bottom)
                    .listRowSeparator(.hidden)
                    .listRowInsets(.init())
            }
            .listStyle(.plain)
            
            TextField("Input", text: $input)
                .padding(.init(top: 0, leading: 15, bottom:0 , trailing: 15))
                .foregroundColor(.blue)
                .textFieldStyle(RoundedBorderTextFieldStyle.init())
        }
    }

    func row(forIndex index: Int) -> some View {
        Text("# (index)")
            .frame(maxWidth: .infinity)
            .background(index % 2 == 0 ? .yellow : .green )
    }
}

I face a problem ocuring when the list is scrolled to the very bottom and software keyboard is shown. At the moment the keyboard starts appearing on the screen the whole context of the list is instantly pushed to the bottom, by the value that’s equal the height of TextField. See attached gif.

That makes sense to some extend as the TextField is respecting keyboard safe area and thus is pushed to the top together with keyboard. But looks that at the same time List is expanding by the space taken before by TextView.

But how to avoid this "jump" of the List content? It definitely has something to do with safe area, I tried playing with properties like .ignoresSafeArea but without any success.

There is no effect like this when using ScrollView with VStack or LazyVStack, but for some reasons I need to stick with List.

Demo

Edit: I’ve tried providing TextField as a custom safeAreaInset, this fixes the problem for showing the keyboard, but there is another glitch when the keyboard is being hidden, similarly, the content is pushed to much to the bottom.

var body: some View {
    List ((1...40), id: .self) { index in
        row(forIndex: index)
            .padding(.bottom)
            .listRowSeparator(.hidden)
            .listRowInsets(.init())
    }
    .listStyle(.plain)
    .safeAreaInset(edge: .bottom) {
        TextField("Input", text: $input)
            .padding(.init(top: 0, leading: 15, bottom:0 , trailing: 15))
            .foregroundColor(.blue)
            .textFieldStyle(RoundedBorderTextFieldStyle.init())
    }
}

2

Answers


  1. My simulator isn’t working quite right. Does this code work for you?
    I tried sticking the text field ontop using Zstack and a few blocks to make sure the list is always visible.

    struct ContentView: View {
        @State var input: String = ""
        
        var body: some View {
            ZStack(alignment: .bottom) {
                VStack(alignment: .center) {
                    List ((1...40), id: .self) { index in
                        row(forIndex: index)
                            .padding(.bottom)
                            .listRowSeparator(.hidden)
                            .listRowInsets(.init())
                    }
                    .listStyle(.plain)
                    
                    // For spacing.
                    Rectangle()
                        .frame(maxWidth: .infinity)
                        .frame(height: 1)
                    Rectangle()
                        .frame(height: 40)
                        .frame(maxWidth: .infinity)
                        .foregroundColor(.white)
                    
                }
                
                // sit ontop of the view so it doesnt interupt it.
                TextField("Input", text: $input)
                    .padding(.init(top: 0, leading: 15, bottom:0 , trailing: 15))
                    .foregroundColor(.blue)
                    .textFieldStyle(RoundedBorderTextFieldStyle.init())
            }
        }
    
        func row(forIndex index: Int) -> some View {
            Text("# (index)")
                .frame(maxWidth: .infinity)
                .background(index % 2 == 0 ? .yellow : .green )
        }
    }
    
    Login or Signup to reply.
  2. If you don’t mind having some overlap between the TextField and List, an .overlay with the TextField and a Spacer above seems to avoid the jumping.

    VStack {
        List(1 ... 40, id: .self) { index in
            row(forIndex: index)
                .padding(.bottom)
                .listRowSeparator(.hidden)
                .listRowInsets(.init())
        }
        .listStyle(.plain)
    }
    .overlay {
        VStack {
            Spacer()
            TextField("Input", text: $input)
                .padding(.init(top: 0, leading: 15, bottom: 0, trailing: 15))
                .foregroundColor(.blue)
                .textFieldStyle(RoundedBorderTextFieldStyle())
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search