Reading time: 1 min

A common issue in SwiftUI is that, when the keyboard appears, it covers up a part of your UI. This is especially problematic if it overlaps the exact TextField you're editing, so that you can't even see what you're typing!

Take this credit card input field at the bottom of a screen:

If you open the keyboard up, it gets covered up completely:

Use the following code to make your views adjust their bottom padding according to keyboard visibility.

The end result looks like this:

The code uses:

  • UIResponder notifications to keep track when the keyboard is shown or hidden.
  • Combine to register the notifications and update the view state.
  • GeometryReader to dynamically update the bottom padding of the view.
  • ViewModifier to apply the behavior to any view without replacing it.
import SwiftUI
import Combine

struct KeyboardAdaptive: ViewModifier {
    @State var bottomPadding: CGFloat = 0

    func body(content: Content) -> some View {
        GeometryReader { geometry in
            content
                .padding(.bottom, self.bottomPadding)
                .animation(.easeOut(duration: 0.16))
                .onAppear(perform: {
                    NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillShowNotification)
                        .merge(with: NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillChangeFrameNotification))
                        .compactMap { notification in
                            notification.userInfo?["UIKeyboardFrameEndUserInfoKey"] as? CGRect
                    }
                    .map { rect in
                        rect.height - geometry.safeAreaInsets.bottom
                    }
                    .subscribe(Subscribers.Assign(object: self, keyPath: \.bottomPadding))

                    NotificationCenter.Publisher(center: NotificationCenter.default, name: UIResponder.keyboardWillHideNotification)
                        .compactMap { notification in
                            CGFloat.zero
                    }
                    .subscribe(Subscribers.Assign(object: self, keyPath: \.bottomPadding))
                })
        }
    }
}

extension View {
    func keyboardAdaptive() -> some View {
        ModifiedContent(content: self, modifier: KeyboardAdaptive())
    }
}

Then, just use keyboardAdaptive() with the superview of your TextField:

VStack {
    TextField("My textfield", $text)
}.keyboardAdaptive()

Next Post Previous Post