4

First off. I know the SwiftUI ScrollView has limitations. I am hoping to get around that here.

My problem is I have a chat box...so a VStack with a ScrollView and a text input. I am able to get the text input to be keyboard adaptive...i.e. when you type it shifts up with the keyboard.

The issue is that the ScrollView loses its scroll position. So, if you scroll to the bottom and then activate the keyboard...you can't see the bottom anymore. It's almost like a ZStack.

What I want is to mimic the same behavior as WhatsApp which maintains scroll position while typing a new message.

I know about the ScrollViewReader and scrollTo...that seems to be a hack in this case and I'm hoping to avoid it. If anything, maybe there is a layout related fix?

2 Answers 2

3

By a stroke of luck I was able to solve this by adding .keyboardAdaptive() to both the ScrollView and the text input as well as changing from padding to offset

struct KeyboardAdaptive: ViewModifier {
    @State private var keyboardHeight: CGFloat = 0

    @State var offset: CGFloat
    
    func body(content: Content) -> some View {
        content
            .offset(y: -keyboardHeight)
            .onReceive(Publishers.keyboardHeight) {
                self.keyboardHeight = $0 == 0 ? 0 : $0 - offset
            }
    }
}
Sign up to request clarification or add additional context in comments.

6 Comments

This looks like a great answer @Timmerz, but I am unsure where Publishers.keyboardHeight comes from 😕
is there a native way to get keyboard height?
In the provided snippet @State var offset never changes. Could you, please, elaborate on what value it should take?
But if you apply KeyboardAdaptive to the ScrollView you can never scroll to the top of the content, won't you?
|
2

Maybe a better option. If you are using iOS 14+ with scrollview or have the option to use scrollview.

https://developer.apple.com/documentation/swiftui/scrollviewproxy https://developer.apple.com/documentation/swiftui/scrollviewreader

Below might help

        ScrollViewReader { (proxy: ScrollViewProxy) in
            ScrollView {
                view1().frame(height: 200)
                view2().frame(height: 200)

                view3() <-----this has textfields 
                    .onTapGesture {
                        proxy.scrollTo(1, anchor: .center)
                    }
                    .id(1)

                view4() <-----this has text editor
                    .onTapGesture {
                        proxy.scrollTo(2, anchor: .center)
                    }
                    .id(2)

                view5().frame(height: 200)
                view6().frame(height: 200)
                submtButton().frame(height: 200)
            }
        }

imp part from above is

         anyView().onTapGesture {
              proxy.scrollTo(_ID, anchor: .center)
         }.id(_ID)

Hope this helps someone :)

1 Comment

it doesn't work for me without DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(1)) {

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.