V8tr / KeyboardAvoidanceSwiftUI

How to move SwiftUI view up when keyboard appears https://www.vadimbulavin.com/how-to-move-swiftui-view-when-keyboard-covers-text-field/
https://www.vadimbulavin.com
The Unlicense
327 stars 36 forks source link

Re-pad the view on re-render while keyboard continues to show #6

Open wearhere opened 4 years ago

wearhere commented 4 years ago

The current implementation will only update the padding if the keyboard shows or hides. But what if the view re-renders, moving the first responder up or down, while the keyboard is showing?

This happens in my app, which is a tutorial for setting up a custom keyboard. My view is like this:

struct SwitchToKeyboardView: View {
    @EnvironmentObject var keyboardState: KeyboardState

    @State var dummyText = ""
    @State var dummyInputIsEditing = true

    @ViewBuilder
    var body: some View {
        VStack {
            if keyboardState.hasBeenOpened {
                Text("Great!").font(.title)
                Text("Searchboard is now ready")
                Text("to use in any app.")
            } else {
                Text("Tap and hold the ") + Text("Globe").bold() + Text(" icon")
                Text("Tap ") + Text("Searchboard").bold() + Text(" to switch keyboards")
            }
            // Use an invisible text view to bring up the keyboard and to
            // permit the keyboard to tell us that we're opened (see
            // `KeyboardState`). We don't use a text field since you can't
            // force those to become first responder
            // https://stackoverflow.com/a/56508132/495611 . Keep the text
            // view visible even after the user has switched, the first
            // time that they switch, so that they can play around with the
            // keyboard.
            TextView(text: $dummyText, isEditing: $dummyInputIsEditing).frame(width: 0, height: 0, alignment: .center)
        }
        .keyboardAdaptive()
    }
}

Where TextView is an instance of this. I won't get into the implementation of KeyboardState unless you like, but I have a mechanism where once my custom keyboard opens, it causes keyboardState.hasBeenOpened to become true, asynchronously with respect to the keyboard changing.

You may notice that the text above the field grows bigger when the keyboard has been opened, so the padding needs to increase. But it doesn't, because the update comes in after the keyboard has already changed.


Here's a PR that's a stab at re-rendering the ViewModifier when the keyboard height changes or when a client-provided publisher fires. In my case, that publisher is a binding to keyboardState.hasBeenOpened: I change my .keyboardAdaptive() invocation to look like

.keyboardAdaptive(rerender: keyboardState.$hasBeenOpened.map({ arg -> Any in
    arg
}).eraseToAnyPublisher())

There are a bunch of things I don't like about this approach, but I figured I'd put it up to start a conversation.