Highlight
iOS 15 在 UIKit 中新增
UIKeyboardLayoutGuide和UITrackingLayoutGuide,让开发者用 Auto Layout 约束跟随屏幕键盘、浮动键盘和快捷栏,减少通知监听、frame 计算和手写动画代码。
核心内容
做聊天输入框、评论框、编辑器时,键盘永远是布局里的麻烦点。
过去的 UIKit 代码通常从通知开始。你要监听 keyboardWillShowNotification 和 keyboardWillHideNotification,从 userInfo 里取键盘结束位置,再算出视图应该移动多少。键盘动画时长也要自己取出来,最后手动调用 layoutIfNeeded()。
这个方案能工作,但边界很多。键盘可能被收起,安全区域会改变高度。iPad 上还有浮动键盘、拆分键盘、硬件键盘快捷栏。你的 App 进入 Split View 后,键盘只覆盖窗口的一部分。每多一个形态,就多一段判断。
iOS 15 把键盘放进了 Auto Layout。UIView 新增 keyboardLayoutGuide,它代表键盘在当前 App 窗口中占据的空间。你把输入框、工具栏或内容视图约束到这个 guide,系统会跟随键盘弹出、收起和高度变化自动更新布局。
这次更新还包含 UITrackingLayoutGuide。它可以根据 guide 是否接近某条边,自动启用或停用一组约束。浮动键盘靠近顶部时,输入工具栏可以回到底部;浮动键盘靠近左侧时,图片视图可以让出空间。
从避开键盘到把键盘纳入布局
键盘以前像一个外部事件。它弹出来,App 再补救。
iOS 15 的思路更直接:键盘也是布局空间。开发者不再读取 frame 做数学计算,改为声明视图和键盘之间的关系。键盘怎么动画,约束就怎么更新。
这对 iPad 更重要。用户可以移动浮动键盘,可以切换拆分键盘,也可以接上硬件键盘。keyboardLayoutGuide 默认处理常见场景;打开 followsUndockedKeyboard 后,布局还能跟随浮动键盘的位置。
详细内容
用通知处理键盘的旧写法
(01:31)演讲先展示了 iOS 15 之前的典型写法:创建一个自定义 layout guide,监听键盘通知,再用通知里的 frame 和动画信息更新约束。
import UIKit
final class NotificationKeyboardViewController: UIViewController {
private let textView = UITextView()
private let keyboardGuide = UILayoutGuide()
private var keyboardHeight: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(textView)
view.addLayoutGuide(keyboardGuide)
textView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
textView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
textView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
keyboardGuide.bottomAnchor.constraint(equalTo: view.bottomAnchor),
keyboardGuide.topAnchor.constraint(equalTo: textView.bottomAnchor)
])
keyboardHeight = keyboardGuide.heightAnchor.constraint(equalToConstant: view.safeAreaInsets.bottom)
keyboardHeight.isActive = true
NotificationCenter.default.addObserver(
self,
selector: #selector(respondToKeyboard),
name: UIResponder.keyboardWillShowNotification,
object: nil
)
}
@objc func respondToKeyboard(notification: Notification) {
let info = notification.userInfo
if let endRect = info?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
var offset = view.bounds.size.height - endRect.origin.y
if offset == 0.0 {
offset = view.safeAreaInsets.bottom
}
let duration = info?[UIResponder.keyboardAnimationDurationUserInfoKey] as? TimeInterval ?? 2.0
UIView.animate(withDuration: duration, animations: {
self.keyboardHeight.constant = offset
self.view.layoutIfNeeded()
})
}
}
}
关键点:
keyboardGuide是开发者自己创建的UILayoutGuide,用于表示键盘上方的可用空间。keyboardGuide.bottomAnchor绑定到view.bottomAnchor,让 guide 从屏幕底部开始计算。keyboardGuide.topAnchor绑定到textView.bottomAnchor,让文本视图底部跟随 guide 顶部。keyboardHeight保存 guide 高度约束,后续通过修改constant改变布局。NotificationCenter.default.addObserver注册键盘显示通知。respondToKeyboard从userInfo读取keyboardFrameEndUserInfoKey。offset用视图高度减去键盘结束位置的y值,得到键盘覆盖高度。offset == 0.0时回退到safeAreaInsets.bottom,处理键盘离开屏幕的情况。keyboardAnimationDurationUserInfoKey提供键盘动画时长。UIView.animate内更新约束常量并调用layoutIfNeeded(),让自定义 guide 跟随键盘动画。
这段代码说明了旧方案的成本。开发者要同时关心通知、frame、安全区域和动画。
UIKeyboardLayoutGuide 把键盘变成约束目标
(03:09)iOS 15 新增 UIView.keyboardLayoutGuide。它是 UILayoutGuide,有和视图一样的 anchors,用来表示键盘占据的空间。
import UIKit
final class KeyboardLayoutGuideViewController: UIViewController {
private let textView = UITextView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(textView)
textView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
textView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
textView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
textView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
view.keyboardLayoutGuide.topAnchor.constraint(
equalToSystemSpacingBelow: textView.bottomAnchor,
multiplier: 1.0
)
])
}
}
关键点:
view.keyboardLayoutGuide是UIView的属性,iOS 15 提供。topAnchor表示键盘当前占据区域的顶部。textView.bottomAnchor约束到键盘 guide 的顶部,键盘弹出时文本视图不会被遮挡。equalToSystemSpacingBelow使用系统间距,让文本视图和键盘之间保留标准距离。- 代码没有注册键盘通知,也没有读取键盘 frame。
- guide 会匹配键盘弹出和收起动画,也会跟随键盘高度变化。
- guide 会处理安全区域,基础场景不需要开发者再计算底部 inset。
默认情况下,浮动键盘或未停靠键盘出现时,guide 会落在屏幕底部,宽度等于窗口宽度。绑定到 guide 顶部的视图仍然能跟随底部位置。
跟随浮动键盘
(05:07)如果你的界面要真正贴着 iPad 浮动键盘移动,可以打开 followsUndockedKeyboard。
import UIKit
final class FloatingKeyboardViewController: UIViewController {
private let editView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(editView)
editView.translatesAutoresizingMaskIntoConstraints = false
view.keyboardLayoutGuide.followsUndockedKeyboard = true
NSLayoutConstraint.activate([
editView.centerXAnchor.constraint(equalTo: view.keyboardLayoutGuide.centerXAnchor),
editView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor)
])
}
}
关键点:
followsUndockedKeyboard默认是false。- 设置为
true后,guide 会跟随未停靠键盘和浮动键盘的位置。 centerXAnchor让editView在水平方向对齐键盘中心。bottomAnchor让editView紧贴键盘顶部。- 用户拖动浮动键盘时,
editView会随 guide 移动。 - 演讲提醒开发者:打开这个属性后,布局必须考虑更多键盘形态和屏幕边缘。
打开跟随后,键盘位置不再稳定。浮动键盘可能远离所有边,也可能贴近顶部、左侧或右侧。此时要配合 UITrackingLayoutGuide 处理边缘情况。
根据垂直边缘切换约束
(06:46)UIKeyboardLayoutGuide 是 UITrackingLayoutGuide 的子类。它可以保存多组约束,并在 guide 接近或远离某条边时自动切换。
import UIKit
final class VerticalTrackingViewController: UIViewController {
private let editView = UIView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(editView)
editView.translatesAutoresizingMaskIntoConstraints = false
view.keyboardLayoutGuide.followsUndockedKeyboard = true
let awayFromTopConstraints = [
view.keyboardLayoutGuide.topAnchor.constraint(equalTo: editView.bottomAnchor),
]
view.keyboardLayoutGuide.setConstraints(awayFromTopConstraints, activeWhenAwayFrom: .top)
let nearTopConstraints = [
view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: editView.bottomAnchor),
]
view.keyboardLayoutGuide.setConstraints(nearTopConstraints, activeWhenNearEdge: .top)
}
}
关键点:
awayFromTopConstraints在键盘 guide 远离顶部时生效。- 第一条约束把
editView.bottomAnchor连接到keyboardLayoutGuide.topAnchor,让编辑工具栏停在键盘上方。 setConstraints(_:activeWhenAwayFrom:)把这组约束交给 tracking guide 管理。nearTopConstraints在键盘 guide 接近顶部时生效。view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: editView.bottomAnchor)把editView放回安全区域底部。setConstraints(_:activeWhenNearEdge:)指定靠近顶部时启用这组约束。- 当浮动键盘被拖到屏幕顶部附近,顶部约束会停用,底部约束会启用,工具栏不会跑出屏幕。
这个模式适合输入附件栏、格式工具条、评论框。用户把浮动键盘拖到顶部时,工具条可以回到底部,避免被屏幕边缘挤掉。
根据水平边缘切换约束
(07:44)同一套机制也能处理水平移动。演讲示例里,键盘远离左右边缘时,编辑视图跟随键盘居中;键盘靠近某一侧时,图片视图向另一侧移动。
import UIKit
final class HorizontalTrackingViewController: UIViewController {
private let editView = UIView()
private let imageView = UIImageView()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(editView)
view.addSubview(imageView)
editView.translatesAutoresizingMaskIntoConstraints = false
imageView.translatesAutoresizingMaskIntoConstraints = false
view.keyboardLayoutGuide.followsUndockedKeyboard = true
let awayFromSides = [
view.keyboardLayoutGuide.centerXAnchor.constraint(equalTo: editView.centerXAnchor),
imageView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
]
view.keyboardLayoutGuide.setConstraints(awayFromSides, activeWhenAwayFrom: [.leading, .trailing])
let nearTrailingConstraints = [
view.keyboardLayoutGuide.trailingAnchor.constraint(equalTo: editView.trailingAnchor),
imageView.leadingAnchor.constraint(
equalToSystemSpacingAfter: view.safeAreaLayoutGuide.leadingAnchor,
multiplier: 1.0
)
]
view.keyboardLayoutGuide.setConstraints(nearTrailingConstraints, activeWhenNearEdge: .trailing)
let nearLeadingConstraints = [
editView.leadingAnchor.constraint(equalTo: view.keyboardLayoutGuide.leadingAnchor),
view.safeAreaLayoutGuide.trailingAnchor.constraint(
equalToSystemSpacingAfter: imageView.trailingAnchor,
multiplier: 1.0
)
]
view.keyboardLayoutGuide.setConstraints(nearLeadingConstraints, activeWhenNearEdge: .leading)
}
}
关键点:
awayFromSides在键盘远离左边和右边时生效。keyboardLayoutGuide.centerXAnchor对齐editView.centerXAnchor,让编辑视图跟随键盘中心。imageView.centerXAnchor对齐view.centerXAnchor,让图片保持在窗口中间。activeWhenAwayFrom: [.leading, .trailing]要求 guide 同时远离左右两侧。nearTrailingConstraints在键盘靠近右侧时生效。keyboardLayoutGuide.trailingAnchor.constraint(equalTo: editView.trailingAnchor)让编辑视图贴住键盘右边。imageView.leadingAnchor约束到安全区域左边,让图片移到键盘反方向。nearLeadingConstraints处理键盘靠近左侧的对称场景。- tracking guide 会在键盘进入或离开边缘区域时自动启用和停用这些约束。
这个能力适合 iPad 编辑器、画板和聊天界面。浮动键盘移动时,内容可以主动让位,输入控件仍然贴近键盘。
设计时要考虑的键盘形态
(08:55)演讲明确说明了 near 和 awayFrom 的判断规则。
view.keyboardLayoutGuide.followsUndockedKeyboard = true
let dockedKeyboardBehavior = [
view.keyboardLayoutGuide.topAnchor.constraint(equalTo: editView.bottomAnchor)
]
view.keyboardLayoutGuide.setConstraints(dockedKeyboardBehavior, activeWhenAwayFrom: [.top, .leading, .trailing])
let topEdgeBehavior = [
view.safeAreaLayoutGuide.bottomAnchor.constraint(equalTo: editView.bottomAnchor)
]
view.keyboardLayoutGuide.setConstraints(topEdgeBehavior, activeWhenNearEdge: .top)
关键点:
- 停靠键盘被视为靠近底部,远离顶部、左侧和右侧。
- 未停靠键盘和拆分键盘可能远离所有边,也可能靠近顶部。
- 浮动键盘可以靠近任何边,也可以同时靠近两条相邻边。
- 这些规则只在
followsUndockedKeyboard为true时影响布局。 activeWhenAwayFrom: [.top, .leading, .trailing]适合表达“键盘没有贴近顶部和左右边缘”的常规状态。activeWhenNearEdge: .top适合处理键盘拖到顶部后的回退布局。
演讲还提醒不要轻易依赖 keyboardLayoutGuide.widthAnchor。硬件键盘的 adaptive shortcuts bar 在 iOS 15 中宽度会随语言和按钮数量变化;收起后还可能靠近左侧或右侧。
(12:02)多任务也会影响 guide。键盘可能离开你的 App 空间,此时系统会把它当成已收起。App 处于分屏或 Slide Over 时,guide 的尺寸只覆盖键盘位于当前窗口内的那一部分。
view.keyboardLayoutGuide.followsUndockedKeyboard = true
let visibleKeyboardConstraints = [
editView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor)
]
view.keyboardLayoutGuide.setConstraints(visibleKeyboardConstraints, activeWhenAwayFrom: .top)
let safeFallbackConstraints = [
editView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
]
view.keyboardLayoutGuide.setConstraints(safeFallbackConstraints, activeWhenNearEdge: .top)
关键点:
- Split View 和 Slide Over 中,键盘可能只覆盖当前 App 的一部分。
- guide 的大小按当前窗口内被键盘覆盖的部分计算。
- 键盘离开 App 空间时,guide 会按键盘收起处理。
visibleKeyboardConstraints让控件在常规状态下贴近键盘。safeFallbackConstraints给顶部边缘场景提供安全回退位置。- 这类回退约束能避免控件随着浮动键盘跑到屏幕外。
核心启发
1. 聊天输入栏跟随键盘
- 做什么:做一个聊天输入栏,键盘弹出时输入栏自动停在键盘上方。
- 为什么值得做:
keyboardLayoutGuide.topAnchor会匹配键盘动画和高度变化,输入栏不需要手写通知监听。 - 怎么开始:把输入栏的
bottomAnchor约束到view.keyboardLayoutGuide.topAnchor,同时把消息列表底部约束到输入栏顶部。
2. iPad 浮动键盘专用编辑工具条
- 做什么:在笔记 App 中让加粗、列表、插图按钮跟着浮动键盘移动。
- 为什么值得做:
followsUndockedKeyboard = true可以让工具条跟随未停靠键盘位置。 - 怎么开始:开启
view.keyboardLayoutGuide.followsUndockedKeyboard,把工具条中心和底部约束到keyboardLayoutGuide.centerXAnchor与topAnchor。
3. 顶部边缘自动回退的表单
- 做什么:浮动键盘被拖到屏幕顶部时,把表单操作栏移动到安全区域底部。
- 为什么值得做:
UITrackingLayoutGuide能在 guide 靠近顶部时自动切换约束。 - 怎么开始:用
setConstraints(_:activeWhenAwayFrom: .top)管理“贴键盘”约束,用setConstraints(_:activeWhenNearEdge: .top)管理“回到底部”约束。
4. 画板 App 的键盘避让布局
- 做什么:用户移动浮动键盘时,让画布预览或图层面板自动移到键盘另一侧。
- 为什么值得做:tracking guide 可以区分
.leading和.trailing,水平边缘状态可以驱动内容让位。 - 怎么开始:为键盘远离左右边缘、靠近左侧、靠近右侧分别准备约束数组,再交给
keyboardLayoutGuide.setConstraints。
5. 支持硬件键盘快捷栏的编辑器
- 做什么:在接入硬件键盘时,让编辑器底部控件适配 adaptive shortcuts bar 的真实宽度。
- 为什么值得做:iOS 15 的快捷栏宽度不再固定为全屏,
keyboardLayoutGuide能暴露真实 leading 和 trailing 边缘。 - 怎么开始:开启
followsUndockedKeyboard后避免依赖固定宽度,用 guide 的边缘 anchors 和 near/awayFrom 状态决定工具栏位置。
关联 Session
- Focus on iPad keyboard navigation — 继续讲 iPad 和 Mac Catalyst 中的硬件键盘导航、焦点移动和 Tab 循环。
- Direct and reflect focus in SwiftUI — SwiftUI 侧的焦点管理和键盘关闭能力,适合表单和跨平台输入体验。
- What’s new in SwiftUI — 总览 SwiftUI 在 2021 年对文本字段、焦点和键盘交互的改进。
- The keys to a better text input experience — 了解键盘通知和文本输入体验的旧方案,便于迁移到
UIKeyboardLayoutGuide。
评论
GitHub Issues · utterances