loading...
Swift You and I

Keyboard-aware views

diegolavalle profile image Diego Lavalle Originally published at swiftui.diegolavalle.com ・2 min read

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

Posted on by:

diegolavalle profile

Diego Lavalle

@diegolavalle

Mad computer scientist. Builder of apps. Apple Frameworks, Web Standards, Swift, Swift UI, Advanced Javascript, React, React Native, Node.

Swift You and I

Swift You and I is a publication centered around SwiftUI, Combine and related Apple frameworks. Official app (https://swiftui.diegolavalle.com/app) with interactive examples available on the App Store.

Discussion

markdown guide