Keyboard Layout Guide

Apple introduced the keyboard layout guide in iOS 15. It can, once Apple fixes some bugs, make it easier to build layouts that adapt to the keyboard. That’s especially tricky when you want to attach things like toolbars to the floating iPad keyboard.

Managing The Keyboard

The traditional way of managing the keyboard is to register for notifications for when the system shows and hides the keyboard. The notification includes the frame for the keyboard that you can use to adjust your layout. That might mean moving views out of the way of the keyboard or adjusting the content inset of a scroll view. See Split views and unexpected keyboard for an example.

In iOS 15, Apple added a keyboard layout guide (UIKeyboardLayoutGuide). It’s a property of UIView and like all layout guides it has anchors for the edges, height, width, and center.

The layout frame of the keyboard guide matches the area of its owning view covered by the keyboard. By constraining our views to the keyboard layout guide we don’t need to listen for keyboard notifications.

For simple situations you may only need to constrain the bottom of a content view to the top anchor of the keyboard layout guide:

contentView.bottomAnchor.constraint(equalTo:
  view.keyboardLayoutGuide.topAnchor)

When the keyboard is not visible the layout guide is at the bottom of screen with a height matching the safe area insets. The top anchor then matches the bottom of the safe area.

Note: The keyboard layout guide is not available in Interface Builder. You’ll need to build your constraints in code to use it.

Adding A Toolbar Above The Keyboard

The keyboard layout guide gets interesting when you want to do more than just avoid it. For example, I have a small toolbar view that sits at the bottom of the screen. When the keyboard appears I want the toolbar to stick to the top of the keyboard:

Toolbar at top of keyboard on iPhone

To get started we could create fixed constraints to center and pin the toolbar to the top of the keyboard layout guide:

NSLayoutConstraint.activate([
  toolbar.centerXAnchor.constraint(equalTo:
    view.keyboardLayoutGuide.centerXAnchor),
  toolbar.bottomAnchor.constraint(equalTo: 
    view.keyboardLayoutGuide.topAnchor)
])

That works when we have the keyboard docked. On an iPad, if we switch to a floating keyboard the toolbar drops back to the bottom of the screen:

Floating keyboard with toolbar at bottom of screen

By default, the keyboard layout guide does not track the undocked keyboard.

Floating Keyboards

To keep the toolbar with the floating keyboard we first need to configure the keyboard layout guide to track the undocked keyboard:

view.keyboardLayoutGuide.followsUndockedKeyboard = true

Our fixed constraints now almost do the job. The toolbar sticks to the top of the undocked keyboard as we move it around the screen. There are still some problems though. The toolbar can disappear offscreen when the keyboard is at the top of the screen. A bigger problem happens when we split the iPad screen:

Toolbar partially hidden offscreen when floating keyboard crosses split screen divider. Keyboard layout guide in red matches part of keyboard still covering app.

The keyboard guide represents the area of the keyboard that is covering the owning view. I’ve highlighted that in red in the screenshot above. As the keyboard moves across the split-screen divider the width of the guide shrinks to zero. That’s a problem if we’ve constrained our toolbar to the center (or both leading and trailing edges) of the keyboard guide.

The challenge with the undocked, floating keyboard is that there’s no single set of constraints that will work as we move the keyboard around the screen. What we need are adaptive constraints that activate depending on the position of the keyboard.

Adaptive Keyboard Constraints

The UIKeyboardLayoutGuide is more than your usual layout guide. It’s a subclass of UITrackingLayoutGuide which can automatically activate and deactivate constraints depending on how near (or far) it is from an edge.

You don’t activate/deactivate the constraints yourself. Instead you pass your constraints to one of two methods:

// Add tracked constraints to guide
setConstraints(_:activeWhenNearEdge:)
setConstraints(_:activeWhenAwayFrom:)

// Remove all tracked constraints
removeAllTrackedConstraints()

These methods both take an array of constraints and an NSDirectionalRectEdge. The tracking guide automatically activates or deactivates the constraints when the guide gets near or away from the specified edges (leading, trailing, top, bottom). For example:

// Constraint when near top edge
view.keyboardLayoutGuide.setConstraints([nearTop],
  activeWhenNearEdge: .top)

// Constraint when away from top edge
view.keyboardLayoutGuide.setConstraints([awayFromTop],
  activeWhenAwayFrom: .top)

// Constraint when away from leading and trailing edges
view.keyboardLayoutGuide.setConstraints([inMiddle],
  activeWhenAwayFrom: [.leading, .trailing])

Let’s remove our fixed keyboard constraints and start again. We can add constraints for when the layout guide moves near to or away from each of the four edges:

Floating keyboard in middle of iPad screen

Away From The Top Edge

Let’s start with the top edge. When the keyboard is away from the top edge we want the toolbar to sit on top of the keyboard layout guide:

let awayFromTop = toolbar.bottomAnchor.constraint(equalTo:
  view.keyboardLayoutGuide.topAnchor)
awayFromTop.identifier = "KB-awayFromTop"
view.keyboardLayoutGuide.setConstraints([awayFromTop],
  activeWhenAwayFrom: .top)

Away from top toolbar is above floating keyboard

To summarise:

  • When the keyboard guide is away from the top edge we activate the constraint that fixes the bottom anchor of the toolbar to the top anchor of the keyboard guide.
  • When the keyboard guide is near the top edge we deactivate the constraint.

Near The Top Edge

When the keyboard gets near the top edge I want the toolbar to drop to the bottom of the screen. We need a constraint that fixes the bottom anchor of the toolbar to the bottom anchor of the safe area layout guide:

let nearTop = toolbar.bottomAnchor.constraint(equalTo:
  view.safeAreaLayoutGuide.bottomAnchor)
nearTop.identifier = "KB-nearTop"
view.keyboardLayoutGuide.setConstraints([nearTop],
  activeWhenNearEdge: .top)

Toolbar at bottom of screen

In The Middle

When the keyboard is away from both the leading and trailing edges I want the toolbar horizontally centered with the keyboard.

let inMiddle = toolbar.centerXAnchor.constraint(equalTo:
  view.keyboardLayoutGuide.centerXAnchor)
inMiddle.identifier = "KB-inMiddle"
view.keyboardLayoutGuide.setConstraints([inMiddle],
  activeWhenAwayFrom: [.leading, .trailing])

Depending on which other constraints are active the toolbar may be on top of the keyboard or at the bottom of the screen:

toolbar centered above keyboard

Remember that this constraint is only active when the keyboard guide is away from both the leading and trailing edges. It deactivates if we are near either the leading or trailing edges.

Near Leading

For the situation when we are near the leading edge we constrain the leading anchor of the toolbar to the leading anchor of the keyboard guide:

let nearLeading = toolbar.leadingAnchor.constraint(equalTo:
  view.keyboardLayoutGuide.leadingAnchor)
nearLeading.identifier = "KB-nearLeading"
view.keyboardLayoutGuide.setConstraints([nearLeading],
  activeWhenNearEdge: .leading)

Floating keyboard across split screen divider. App on the right. Toolbar on top of keyboard to the right of divider

Remember that as the keyboard moves over the split screen divider the keyboard guide only matches the part of the keyboard still covering our app.

Near Trailing

Finally when we are near the trailing edge we constrain the toolbar to the trailing anchor of the keyboard guide:

let nearTrailing = toolbar.trailingAnchor.constraint(equalTo:
  view.keyboardLayoutGuide.trailingAnchor)
nearTrailing.identifier = "KB-nearTrailing"
view.keyboardLayoutGuide.setConstraints([nearTrailing],
  activeWhenNearEdge: .trailing)

Floating keyboard across split screen divider. App on the left. Toolbar on top of keyboard to the left of divider

Docked Keyboard

When we have the keyboard docked, it’s near the bottom and away from all other edges. That means my away from top and in the middle constraints are active pinning the toolbar to the top and horizontal center of the keyboard:

Docked keyboard with center toolbar

Debugging Tips

This is hard to get right. You need to consider docked, undocked and split screen configurations. For every keyboard position you need enough constraints for a valid layout without creating conflicts. When in split-screen don’t forget to test with the app on both the left and right of the divider.

The view debugger is a huge help in understanding which constraints are active as you move the undocked keyboard around. I strongly suggest you add identifiers to each of the constraints. I prefixed my constraint identifiers with KB- so that I can easily filter for them in the view debugger.

View debugger with KB- in filter bar

For example, here is what I see when the undocked keyboard is over the split-screen in the middle of the screen with the app to the right. The away from top and near leading constraints are active:

KB-awayFromTop and KB-nearLeading constraints active

As mentioned before, when we have a docked keyboard, the guide is near the bottom and away from all other edges. The view debugger shows our away from top and in the middle constraints active:

KB-awayFromTop and KB-inMiddle constraints active

Apple’s Sample Code

Apple’s own sample code doesn’t always get it right either (FB9725705). Here’s how their toolbar looks with an undocked keyboard in center screen:

Toolbar above and centered with undocked keyboard

The problem comes in split-screen with the app on the right. Move the keyboard near the leading edge and also near the bottom and the toolbar gets squeezed:

Toolbar is squeezed

You can see the problem in the view debugger:

editViewToDockedKeyboardTrailing is active

That editViewToDockedKeyboardTrailing constraint looks suspicious. It’s constraining the toolbar to the trailing edge of the keyboard, squeezing it up against the leading edge. From the name we can guess it’s only supposed to be active for a docked keyboard not when we are floating. Here’s the setup code:

let editViewToDockedKeyboardTrailing = view.keyboardLayoutGuide.trailingAnchor.constraint(equalTo:
  editView.trailingAnchor)
editViewToDockedKeyboardTrailing.identifier = "editViewToDockedKeyboardTrailing"
        
let nearBottomConstraints = [ keyboardToImageView, editViewToDockedKeyboardTrailing ]
view.keyboardLayoutGuide.setConstraints(nearBottomConstraints,
  activeWhenNearEdge: .bottom)

Being near the bottom edge is not enough to guarantee a docked keyboard. It also needs to be away from leading and trailing edges. My guess is that they only tested with the app on the left of the split screen (that’s the quickest to activate with the multitasking controls in iOS 15). Treat this as a warning that you should test all keyboard configurations.

Bugs

Unfortunately, at the time of writing, I’m hitting a fairly serious problem with the keyboard layout guide (FB9733654). I can’t recommend using the guide until Apple fixes the problem.

In some situations, the keyboard layout guide frame is not getting updated. For example, I added a button to my view controller that shows another view controller. When I navigate back to the initial view controller the toolbar is no longer visible. The problem happens even when the guide is not tracking the undocked keyboard.

Checking with the view debugger it looks like a new keyboard layout guide is being added to the root view each time my initial view controller appears. Here’s how it looks after I navigate away and back several times:

Four keyboard layout guides attached to root view

The original keyboard layout guide has the correct constraints activated but the layout frame is not set for any of the guides. This ends up leaving my toolbar with an ambiguous position. Let’s hope Apple fixes this soon.

Learn More

If you’re struggling to build adaptive layouts with Auto Layout you might like my book Modern Auto Layout.