iOS Keyboard Caching. The why, the how and the code.

Eliminate the cold start delay and know the keyboard height before it ever appears.

You open your app. You tap into a text field.

Did you catch that?

Go back and try it again. That first tap has a weird subtle hitch while iOS boots up the keyboard process. After that, everything feels instant. But the very first time? Not great.

It's subtle, but once you see it, you can't unsee it.

Notice the delay without cache vs with

The good news? You don't have to live with it.

You can warm things up early and cache what you learn from the first keyboard presentation. That not only removes the initial delay, it also gives you a bonus, you can estimate the keyboard height before it even appears, which makes your animations feel tighter and more intentional.

This builds on the KeyboardManager from Trouble Getting It Up?iioscraft. If you haven’t read that yet, it walks through the hidden animation curve and an observer-based manager that we’re extending here.

Knowing the Height Before It Appears

Sometimes you need the keyboard height before it shows up. Pre-positioning a text field, sizing a sheet that'll shrink, laying out a form. You can't wait for the notification because by then the user already sees the jump.

The fix is to cache the height in UserDefaults every time the keyboard appears, then read it back on subsequent launches. Add these to the KeyboardManager:

Height Caching
private let heightCacheKey = "app.cachedKeyboardHeight"

var estimatedHeight: CGFloat {
    let cached = UserDefaults.standard.double(forKey: heightCacheKey)
    return cached > 0 ? cached : 335
}

private func persistHeight(_ height: CGFloat) {
    guard height > 0 else { return }
    UserDefaults.standard.set(height, forKey: heightCacheKey)
}

Then call persistHeight from the notification handlers in our manager whenever you get a new height:

Notification Handlers
@objc private func handleKeyboardWillShow(_ notification: Notification) {
    guard let endFrame = notification.keyboardEndFrame else { return }
    currentHeight = endFrame.height
    isVisible = true
    persistHeight(endFrame.height)
    notifyObservers(notification)
}

@objc private func handleKeyboardWillChangeFrame(_ notification: Notification) {
    guard let endFrame = notification.keyboardEndFrame,
          endFrame.height > 0, isVisible
    else { return }
    currentHeight = endFrame.height
    persistHeight(endFrame.height)
}

Now you can use estimatedHeight anywhere to get a best-guess before the keyboard shows:

let estimated = KeyboardManager.shared.estimatedHeight
scrollView.contentInset.bottom = estimated

335 points is the hardcoded fallback for a standard iPhone keyboard in portrait on iOS 26. Close enough until you have real data — after one keyboard appearance you'll have the exact number for that user's device and language, and because we run a cache on the scene delegate this should happen before you actually need to use the fallback.

Killing the Cold Start

The keyboard runs out-of-processdeveloper.apple.comKeyboards and Input for security. That process needs to cold-start the first time it's needed, which is where the delay comes from. Joseph Smith (@_jsmth)x.com posted about this: you can warm it up on launch by briefly making a throwaway text field the first responder.

UIResponder+PreloadKeyboard.swift
extension UIResponder {
    private static var didPreload = false

    static func preloadKeyboard() {
        guard !didPreload else { return }
        didPreload = true

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            guard let scene = UIApplication.shared.connectedScenes
                    .first(where: { $0.activationState == .foregroundActive })
                    as? UIWindowScene,
                  let window = scene.windows.first(where: \.isKeyWindow)
            else { return }

            KeyboardManager.shared.isPreloading = true

            let dummyField = UITextField()
            dummyField.autocorrectionType = .no
            window.addSubview(dummyField)
            dummyField.becomeFirstResponder()
            dummyField.resignFirstResponder()
            dummyField.removeFromSuperview()

            KeyboardManager.shared.isPreloading = false
        }
    }
}

The field never renders on screen. The keyboard process loads, measures itself, and goes back to sleep. As a bonus, this triggers a keyboard notificationdeveloper.apple.comkeyboardWillShowNotification — which means our manager caches the real keyboard height before the user ever taps a text field. First tap is now instant and you have accurate height data from the start.

The isPreloading flag suppresses observer callbacks during the preload. Without it, any view controller already registered as an observer would see a keyboard show/hide cycle and briefly animate its input bar up and back down on launch. Add the property and guard to the KeyboardManager:

Preload Suppression
var isPreloading = false

private func notifyObservers(_ notification: Notification) {
    guard !isPreloading else { return }
    observers = observers.filter { $0.value.object != nil }
    for (_, reference) in observers {
        reference.object?.keyboardManager(
            self,
            keyboardWillTransitionTo: currentHeight,
            visible: isVisible,
            notification: notification
        )
    }
}

The manager still updates currentHeight and caches the height during the preload — it just doesn't tell anyone about it.

Setup

One line in SceneDelegate:

UIResponder.preloadKeyboard()

The manager is a singleton — it starts listening for keyboard notificationsdeveloper.apple.comkeyboardWillShowNotification the first time anything accesses .shared. The preload call warms up the keyboard process in the background, the user never sees it, and the notification it triggers populates the height cache automatically. ✨