Keyboard-aware views

Diego Lavalle
Swift You and I
Published in
3 min readAug 2, 2020

Sometimes we want to give our views a chance to react to the software keyboard being pulled up. This could be particularly useful in forms with text input where the keyboard might cover the very field we are typing into. We can solve this in SwiftUI with a little help from some old friends.

UIKit’s UIResponder is an interface that provides notifications for a variety of UI-related events, including keyboard events. By subscribing to keyboardDidShowNotification we get the timely information we need via keyboardFrameEndUserInfoKey which contains the keyboard frame.

We’ll start by creating an ObservableObject containing the current keyboard frame. There can only be one software keyboard so we’ll make our class a singleton. The frame property is published so that we can subscribe to it from our views.

import Foundation
import UIKit
import CoreGraphics
import Combine
class KeyboardProperties: ObservableObject {

static let shared = KeyboardProperties()

@Published var frame = CGRect.zero
var subscription: Cancellable? init() {
subscription =
}
}

Now to populate our frame property we’ll need to tap into NotificationCenter. In particular we are interested in the following UIResponder events: keyboardDidShowNotification and keyboardDidHideNotification. We’ll use the Combine framework to filter out the information we want from these notifications - namely the keyboard frame - and assign it to the corresponding instance property.

subscription = NotificationCenter.default
.publisher(for: UIResponder.keyboardDidShowNotification)
.compactMap { $0.userInfo }
.compactMap {
$0[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect
}
.merge(
with: NotificationCenter.default.publisher(for: UIResponder.keyboardDidHideNotification).map { _ in
CGRect.zero
}
)
.assign(to: \.frame, on: self)

For demonstration purposes we are going to create a simple SwiftUI view based on a vertical stack with a text field at the bottom. Without any additional logic, the text field will be covered by the rising software keyboard.

@State var textValue = ""var body: some View {
VStack {
// Pushes TextField to the bottom of view
Spacer()
// Text field would be covered by keyboard
TextField("Enter some text", text: $textValue)
}
}

To help us we can now bring in our KeyboardProperties singleton using the @ObservedObject property wrapper. Since we’re specifically interested in the keyboard’s height, we’ll put that in a calculated kbHeight property. Finally, we use this height to offset our text field, so that it is no longer covered up by the keyboard.

@ObservedObject var keyboardProps = KeyboardProperties.sharedvar kbHeight: CGFloat {
keyboardProps.frame.height
}
var body: some View {
VStack {

TextField("Enter some text", text: $textValue)
// Text field will now be pushed up by the keyboard
.offset(y: -kbHeight)
// A little animation to soothe things up
.animation(.easeIn(duration: 0.2))
}
}

That’s it, we have successfully made our form keyboard-aware solving a serious user experience issue. Please check out the associated Working Example to see this technique in action.

FEATURED EXAMPLE

All Rise! Software keyboard in da view

Originally published at Swift You and I

--

--

Diego Lavalle
Swift You and I

Mad computer scientist. Builder of Apps. Early adopter. Web Standards and Apple Frameworks specialist.