为 iPadOS 指针交互构建体验
Build for the iPadOS pointer
2020年6月23日
一句话判断
iPadOS 13.4 引入了触控板和鼠标支持,但很多 App 还没有适配指针交互。这个 Session 教你如何利用 UIPointerInteraction 让你的 App 在连接外设时有原生级的指针体验。
这场 Session 讲了什么
iPadOS 的指针系统设计理念是”indirect touch”——指针是手指的延伸而非传统桌面系统的鼠标指针。系统默认会为所有 UIKit 控件提供基本的指针反馈(比如悬浮时的高亮),但自定义的视图需要你手动适配 UIPointerInteraction。
Session 详细讲解了指针交互的三个层次。第一层是基本适配:为自定义视图添加 UIPointerInteraction,让指针悬浮时有视觉反馈。第二层是区域定制:通过 UIPointerRegion 精确定义哪些区域可以触发指针效果。第三层是自定义指针样式:通过 UIPointerStyle 完全控制指针的视觉效果,包括自动隐藏、缩放动画等。Session 还介绍了如何在 UICollectionView 和 UITableView 中利用指针增强交互体验。
值得深挖的点
指针的区域映射(Region Mapping)
iPadOS 指针的核心机制是”区域映射”:指针进入一个交互区域时,整个区域的形状会”吸附”到指针上,形成一个视觉上统一的”瞄准”效果。通过 UIPointerRegion 你可以精确控制哪些区域响应悬浮,还可以设置区域的 rect 来调整吸附范围。如果你不提供 Region,系统会使用视图的 bounds 作为默认区域。
UITargetedPointer vs UIPointerEffect
Apple 提供了两种内置的指针效果。UITargetedPointerEffect(原名 highlight 效果)会在目标视图上显示一个覆盖层,适合按钮等离散控件。UIPointerEffect(原名 lift 效果)会让目标视图轻微抬起并增加阴影,适合卡片和可拖拽的元素。正确选择效果类型对用户体验至关重要。
代码片段
为自定义视图添加指针交互
场景:为自定义的圆形按钮添加悬浮效果。
import UIKit
class CircleButton: UIView {
private let pointerInteraction = UIPointerInteraction()
override init(frame: CGRect) {
super.init(frame: frame)
setupView()
}
private func setupView() {
// 添加指针交互
addInteraction(pointerInteraction)
pointerInteraction.delegate = self
// 基础样式
backgroundColor = .systemBlue
layer.cornerRadius = bounds.width / 2
}
required init?(coder: NSCoder) {
fatalError("init(coder:) 未实现")
}
}
// 实现指针交互代理
extension CircleButton: UIPointerInteractionDelegate {
func pointerInteraction(_ interaction: UIPointerInteraction,
styleFor region: UIPointerRegion) -> UIPointerStyle? {
// 创建目标区域
let targetedPreview = UITargetedPreview(view: self)
// 使用 highlight 效果
let effect = UIPointerEffect.highlight(
UITargetedPreview(view: self)
)
// 创建指针样式
let style = UIPointerStyle(effect: effect)
return style
}
func pointerInteraction(_ interaction: UIPointerInteraction,
regionFor request: UIPointerRegionRequest) -> UIPointerRegion? {
// 定义可交互的圆形区域
return UIPointerRegion(rect: bounds)
}
}
在 UICollectionView 中增强指针体验
场景:为 CollectionView 的 Cell 添加悬浮效果。
class PhotoGridViewController: UIViewController,
UICollectionViewDelegate {
func collectionView(_ collectionView: UICollectionView,
didSelectItemAt indexPath: IndexPath) {
// 正常的选择处理
}
}
// 通过 UICollectionViewDelegatePointerInteraction 定制 Cell 的指针效果
extension PhotoGridViewController: UIPointerInteractionDelegate {
func pointerInteraction(_ interaction: UIPointerInteraction,
styleFor region: UIPointerRegion) -> UIPointerStyle? {
guard let cell = interaction.view as? UICollectionViewCell else {
return nil
}
// 使用 lift 效果让 Cell 看起来可以被拿起
let effect = UIPointerEffect.lift(UITargetedPreview(view: cell))
return UIPointerStyle(effect: effect)
}
}
自定义指针形状和隐藏行为
场景:在绘图 App 中隐藏系统指针并显示自定义光标。
class CanvasView: UIView {
private let pointerInteraction = UIPointerInteraction()
private var customCursorLayer = CAShapeLayer()
override init(frame: CGRect) {
super.init(frame: frame)
addInteraction(pointerInteraction)
pointerInteraction.delegate = self
}
required init?(coder: NSCoder) {
fatalError("init(coder:) 未实现")
}
}
extension CanvasView: UIPointerInteractionDelegate {
func pointerInteraction(_ interaction: UIPointerInteraction,
styleFor region: UIPointerRegion) -> UIPointerStyle? {
// 隐藏系统指针,使用自定义的光标
return UIPointerStyle(hiddenPointerStyle)
}
// 追踪指针位置来更新自定义光标
func pointerInteraction(_ interaction: UIPointerInteraction,
willEnter region: UIPointerRegion) {
// 显示自定义十字光标
showCrosshairCursor()
}
func pointerInteraction(_ interaction: UIPointerInteraction,
willExit region: UIPointerRegion) {
// 隐藏自定义光标
hideCrosshairCursor()
}
}
最佳实践
已有项目:优先为所有可点击的自定义视图添加 UIPointerInteraction。UIKit 的标准控件(UIButton、UISwitch 等)已经内置了指针支持,但自定义的按钮和卡片需要手动适配。在 iPad 上测试你的 App,用触控板操作一遍所有核心流程,找出缺少指针反馈的地方。
新项目:从设计阶段就考虑指针交互。为不同类型的交互元素选择合适的指针效果——按钮用 highlight,卡片用 lift,绘图区域用自定义光标。确保指针效果不会与手势冲突。
还有什么值得关注
pointerInteraction.isEnabled可以动态控制指针交互的开启和关闭。- iPadOS 14 新增了 pointer shapes API,可以为不同工具设置不同的指针形状(如画笔、橡皮擦)。
- 搭配 Session 10094(Handle trackpad and mouse input)可以了解更多手势相关的适配细节。