WWDC Quick Look 💓 By SwiftGGTeam
Your guide to keyboard layout

Your guide to keyboard layout

观看原视频

Highlight

iOS 15 在 UIKit 中新增 UIKeyboardLayoutGuideUITrackingLayoutGuide,让开发者用 Auto Layout 约束跟随屏幕键盘、浮动键盘和快捷栏,减少通知监听、frame 计算和手写动画代码。

核心内容

做聊天输入框、评论框、编辑器时,键盘永远是布局里的麻烦点。

过去的 UIKit 代码通常从通知开始。你要监听 keyboardWillShowNotificationkeyboardWillHideNotification,从 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 注册键盘显示通知。
  • respondToKeyboarduserInfo 读取 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.keyboardLayoutGuideUIView 的属性,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 会跟随未停靠键盘和浮动键盘的位置。
  • centerXAnchoreditView 在水平方向对齐键盘中心。
  • bottomAnchoreditView 紧贴键盘顶部。
  • 用户拖动浮动键盘时,editView 会随 guide 移动。
  • 演讲提醒开发者:打开这个属性后,布局必须考虑更多键盘形态和屏幕边缘。

打开跟随后,键盘位置不再稳定。浮动键盘可能远离所有边,也可能贴近顶部、左侧或右侧。此时要配合 UITrackingLayoutGuide 处理边缘情况。

根据垂直边缘切换约束

06:46UIKeyboardLayoutGuideUITrackingLayoutGuide 的子类。它可以保存多组约束,并在 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)

关键点:

  • 停靠键盘被视为靠近底部,远离顶部、左侧和右侧。
  • 未停靠键盘和拆分键盘可能远离所有边,也可能靠近顶部。
  • 浮动键盘可以靠近任何边,也可以同时靠近两条相邻边。
  • 这些规则只在 followsUndockedKeyboardtrue 时影响布局。
  • 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.centerXAnchortopAnchor

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

评论

GitHub Issues · utterances