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:
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:
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:
By default, the keyboard layout guide does not track the undocked keyboard.
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:
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
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:
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)
- 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)
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:
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.
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)
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.
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)
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:
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.
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:
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:
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:
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:
You can see the problem in the view debugger:
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.
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:
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.
If you’re struggling to build adaptive layouts with Auto Layout you might like my book Modern Auto Layout.