Keyboard-aware views

diegolavalle

Diego Lavalle

Posted on August 2, 2020

Keyboard-aware views

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.

Keyboard-aware views

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.shared

var 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

šŸ’– šŸ’Ŗ šŸ™… šŸš©
diegolavalle
Diego Lavalle

Posted on August 2, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

Codable observable objects
ios Codable observable objects

August 1, 2020

Form Validation with Combine
ios Form Validation with Combine

January 20, 2020

Flip clock in SwiftUI
ios Flip clock in SwiftUI

October 22, 2019

Keyboard-aware views
ios Keyboard-aware views

August 2, 2020