WWDC Quick Look 💓 By SwiftGGTeam
Build for the iPadOS pointer

Build for the iPadOS pointer

观看原视频

Highlight

iPadOS 的指针系统设计理念是”indirect touch”——指针是手指的延伸而非传统桌面系统的鼠标指针。许多系统组件和部分标准控件已有内置指针支持;UIButton 等控件可通过 API 启用和定制,自定义 UI 需要用 UIPointerInteraction 适配。

核心内容

iPadOS 13.4 给 iPad 增加了通用指针设备支持。问题随之出现:iPad 的界面长期围绕手指触控设计,很多控件的可点区域、反馈方式和拖动手势都假设用户直接触摸屏幕。如果直接把桌面鼠标模型搬进来,自定义按钮、画布、文本区域和工具栏很容易变得生硬。

Apple 的建议是从高层 API 往下走。UIBarButtonItemUISegmentedControlUIMenuControllerUIScrollViewUITextViewUIDragInteractionUIContextMenuInteraction 已经获得了系统级支持。先使用这些现成行为,再处理自己的 custom view,能让 App 和系统保持一致(03:16)。

真正需要开发者动手的地方,是那些看起来像控件、但 UIKit 无法自动理解语义的视图。Session 用一个 Quilt Simulator 演示:右下角的 ruler 按钮需要更合适的高亮范围,右上角的线轴按钮需要 lift 效果,中央缝纫区域需要十字准星和按网格吸附的指针区域。

这场分享的主线很清楚:先让系统控件拿到默认指针反馈,再用 UIButton 的 convenience API 微调按钮,最后用 UIPointerInteractionUIPointerRegionUIPointerStyle 和 delegate 动画处理自定义 UI。

详细内容

1. 先启用 UIButton 的内置指针效果

06:04UIButton 提供了两阶段接口。第一步打开 isPointerInteractionEnabled,系统会根据按钮外观、大小和内容给出默认效果。第二步用 pointerStyleProvider 接收系统提议的 effect 和 shape,再替换其中一部分。

// Enable the button's built-in pointer interaction.
myButton.isPointerInteractionEnabled = true

// Customize the default interaction effect.
myButton.pointerStyleProvider = { button, proposedEffect, proposedShape -> UIPointerStyle? in
    // In this example, we'll switch to using the .lift effect by creating a new
    // UIPointerEffect with the .lift type using the proposedEffect's preview.
    return UIPointerStyle(effect: .lift(proposedEffect.preview), shape: proposedShape)
}

关键点:

  • isPointerInteractionEnabled = true 让按钮进入系统指针体系。
  • pointerStyleProvider 在悬浮时被调用,适合做按钮级定制。
  • proposedEffectproposedShape 来自系统判断,先复用它们能减少不必要的视觉偏差。
  • .lift(proposedEffect.preview) 适合有自身形状的按钮,例如演示里的线轴按钮。
  • 返回 nil 时系统继续使用默认样式;返回 UIPointerStyle 时使用自定义样式。

2. 用 content effect 调整悬浮范围

07:05)指针样式分为 content effect 和 shape customization。content effect 会让指针变形为某个 view,并对该 view 施加视觉处理。Session 里 ruler 按钮的问题是高亮区域太紧,解决方式是给 pointer shape 增加一点 padding。

// Create a UIPointerStyle that applies the .highlight effect.

// Outset the view's frame so the pointer shape has some generous padding around the view's contents.
// Note that this frame must be in the provided UITargetedPreview's container's coordinate space.
// In the majority of cases (where the preview doesn't have a custom container), this is just the view's superview.
let rect = myView.frame.insetBy(dx: -8.0, dy: -4.0)
let preview = UITargetedPreview(view: myView)

return UIPointerStyle(effect: .highlight(preview), shape: .roundedRect(rect))

关键点:

  • myView.frame.insetBy(dx: -8.0, dy: -4.0) 使用负值扩大矩形,让指针更早吸附到按钮。
  • UITargetedPreview(view:) 告诉系统哪个视图参与 hover 视觉效果。
  • .highlight(preview) 对应工具栏和按钮常见的高亮效果。
  • .roundedRect(rect) 决定指针变形后的外轮廓。
  • 注释里强调坐标空间要和 preview 的 container 对齐,否则高亮位置会偏移。

3. 用 shape customization 表达上下文

08:02)shape customization 只改变指针形状和移动约束。文本区域是典型例子:指针变成 vertical beam,并被约束在垂直轴上,移动时像贴在文本行上。

// Create a UIPointerStyle that changes the pointer into a vertical beam.

let beamLength = myFont.lineHeight
return UIPointerStyle(shape: .verticalBeam(length: beamLength), constrainedAxes: .vertical)

关键点:

  • myFont.lineHeight 让 beam 高度和当前文字排版一致。
  • .verticalBeam(length:) 表达文本插入或精确定位场景。
  • constrainedAxes: .vertical 让指针沿指定轴线表现出约束感。
  • 这个模式同样出现在 Quilt Simulator 的直线引导模式里:指针在水平网格线上吸附,帮助用户缝出直线(17:45)。

4. 用 UIPointerInteraction 管理自定义区域

13:09)自定义画布没有 UIButton 这类语义,Session 选择直接添加 UIPointerInteraction。默认情况下,interaction 会覆盖整个 view;如果要在子区域内切换样式,就实现 UIPointerInteractionDelegate 的 region request 和 style request,把不同 UIPointerRegion 映射到不同 UIPointerStyle

这套机制还能扩展有效区域。Session 在 19:20 提到,可以提供更大的 pointer region 来增强“磁性”,但 region 必须落在 interaction view 的 hit-testable area 内。如果 region 超出 view,需要同时覆写 hit-test,让扩展区域能命中该 view(19:20)。

5. 把额外动画接入指针进入和退出

21:31)最后一层 polish 是和指针动画同步调整其他 UI。UIPointerInteractionDelegatewillEnterwillExit 会提供 animator,开发者可以把自己的动画加入同一条动画时间线。Session 用 UISegmentedControl 隐藏 separator 的例子解释这个技巧。

func pointerInteraction(_ interaction: UIPointerInteraction,
                        willEnter region: UIPointerRegion,
                        animator: UIPointerInteractionAnimating) {

    // Fade out separator when entering region.
    animator.addAnimations {
        self.separatorView.alpha = 0.0
    }
}

关键点:

  • willEnter region 在指针进入某个 region 时触发。
  • animator.addAnimations 会把自定义动画接到系统指针动画中。
  • separatorView.alpha = 0.0 调整的是周边 chrome,用来减少 hover 状态下的视觉噪声。
func pointerInteraction(_ interaction: UIPointerInteraction,
                        willExit region: UIPointerRegion,
                        animator: UIPointerInteractionAnimating) {

    // Fade separator back in when exiting region.
    animator.addAnimations {
        self.separatorView.alpha = 1.0
    }
}

关键点:

  • willExit region 用来恢复进入前的界面状态。
  • 使用同一个 animator 能避免 separator 和指针效果节奏不一致。
  • 这类动画适合隐藏分隔线、辅助 chrome 或临时提示,目标是让 hover 状态更干净。

核心启发

  • 补齐自定义工具栏按钮做什么:检查所有通过 customView 放进 bar 的按钮。为什么值得做:系统只能自动处理标准 UIBarButtonItem,custom view 需要自己声明指针行为。怎么开始:先打开 isPointerInteractionEnabled,再用 pointerStyleProvider 复用系统提议的 effect 和 shape。

  • 给画布类界面增加精确指针做什么:在绘图、编辑、排版、地图标注等区域内使用专门的 pointer shape。为什么值得做:Session 的 Quilt Simulator 通过十字准星和轴向约束让点击位置更清楚。怎么开始:给画布添加 UIPointerInteraction,在 delegate 中为不同区域返回 UIPointerStyle(shape:constrainedAxes:)

  • 用 pointer region 放大关键控件的吸附范围做什么:让小按钮、窄把手和密集列表项在指针靠近时更容易被捕捉。为什么值得做:Session 明确提到扩展 region 可以增强磁性,降低精确移动成本。怎么开始:返回比视觉边界更宽的 UIPointerRegion,并确认该区域位于可 hit-test 的 view 范围内。

  • 把 hover 状态和界面 chrome 联动做什么:在指针进入某个区域时隐藏分隔线、弱化背景或显示临时辅助信息。为什么值得做willEnterwillExit 的 animator 可以让这些变化跟系统指针动画同步。怎么开始:实现两个 delegate 方法,把 separatorView.alpha、辅助标签透明度或轻量高亮加入 animator.addAnimations

关联 Session

  • Design for the iPadOS pointer — 从设计角度解释 iPadOS 指针的自适应精度、形变、手势和键盘修饰键。
  • Handle trackpad and mouse input — 讲解指针移动、pointer lock、滚动输入、触控板手势和鼠标按键事件。
  • Build for iPad — 展示如何用多列布局、列表和低模态导航改造更完整的 iPad App。
  • Designed for iPad — 从 iPad 设计模式出发,覆盖 sidebar、拖放、键盘、触控板和自适应布局。

评论

GitHub Issues · utterances