Highlight
iPadOS 18 的 PencilKit 工具栏终于支持自定义工具——你的印章、橡皮、修图工具可以直接放进系统工具栏,并自动获得 Apple Pencil Pro 的 squeeze、barrel roll 和触觉反馈。
核心内容
做绘画 App 的开发者一直面临一个选择:用 PencilKit,工具栏体验好但工具种类受限于系统内置的笔、橡皮、套索;自己做画布和工具栏,工具灵活了,但 squeeze、hover 预览、barrel roll 这些 Apple Pencil Pro 特性要从零实现,工作量巨大。
今年 Apple 给出了一个折中方案——PKToolPickerCustomItem。你可以把自定义工具注册到 PencilKit 工具栏里,用户在系统工具栏中就能切换你的工具。自定义工具选中时,PencilKit 的画布自动停止绘制,你的代码接管渲染。工具栏的颜色选择器、宽度滑块依然可用,你还能传入自己的 UIViewController 来放额外控件。barrel roll、squeeze 这些交互,自定义工具自动获得,零额外代码。
同时,Apple Pencil Pro 带来了三个新的交互能力:squeeze 手势(捏笔杆触发)、barrel roll 角度(转笔杆控制笔触方向)、以及基于位置信息的触觉反馈(对齐辅助线和形状识别时给手感)。PencilKit 内置工具自动支持这些,自定义画布也可以通过新的 UIPencilInteraction、UICanvasFeedbackGenerator 和 SwiftUI 的 .onPencilSqueeze、.sensoryFeedback 来接入。
详细内容
工具栏自定义:toolItems 初始化器
从 iOS 13 起,PKToolPicker 只有默认的全套工具。iPadOS 18 新增了 toolItems 初始化器,让你指定工具栏中工具的顺序和种类(02:39)。你可以放多个同类型的墨水工具——比如一支红笔、一支蓝笔——这对标注类 App 非常实用。
工具栏条目类型包括:PKInkingToolItem(墨水)、PKEraserToolItem(橡皮)、PKLassoToolItem(套索)、PKRulerItem(标尺,点击切换开关)、PKScribbleItem(手写转文字)、PKToolPickerCustomItem(自定义工具),还有一个尾部 UIBarButtonItem 用来放非绘制操作(04:55)。
自定义工具:PKToolPickerCustomItem
自定义工具的核心是 PKToolPickerCustomItem 及其 Configuration 结构体(06:10)。Configuration 定义工具支持的属性——颜色、宽度、不透明度由 PencilKit 提供控件,额外属性通过你传入的 UIViewController 展示。工具图标由你提供一个闭包来渲染,当颜色、宽度或不透明度变化时 PencilKit 自动调用该闭句更新图标;自定义属性变化时你需要手动调用 reloadImage()。
选中自定义工具后,所有关联的 PKCanvasView 自动停止绘制,你的代码接管触摸事件和渲染。PencilKit 只负责工具选择 UI。
Squeeze 手势
Apple Pencil Pro 的 squeeze 手势让用户不用抬手就能调出工具栏或上下文面板(09:20)。系统有一个全局偏好设置,决定 squeeze 触发什么:显示上下文调色板、执行快捷指令、或自定义行为。如果用户选了快捷指令,你的 App 收不到 squeeze 事件。
UIKit 中响应 squeeze:
class MyViewController: UIViewController, UIPencilInteractionDelegate {
func pencilInteraction(_ interaction: UIPencilInteraction,
didReceiveSqueeze squeeze: UIPencilInteraction.Squeeze) {
if UIPencilInteraction.preferredSqueezeAction == .showContextualPalette &&
squeeze.phase == .ended {
let anchorPoint = squeeze.hoverPose?.location ?? myDefaultLocation
presentMyContextualPaletteAtPosition(anchorPoint)
}
}
}
UIPencilInteractionDelegate的pencilInteraction(_:didReceiveSqueeze:)方法接收 squeeze 事件(10:24)preferredSqueezeAction检查用户在系统设置中的偏好squeeze.phase == .ended确保只在手势结束时触发,避免重复响应squeeze.hoverPose?.location获取 squeeze 时笔尖悬停位置,用来定位面板
SwiftUI 中响应 squeeze:
@Environment(\.preferredPencilSqueezeAction) var preferredAction
@State var contextualPalettePresented = false
@State var contextualPaletteAnchor = MyPaletteAnchor.default
var body: some View {
MyView()
.onPencilSqueeze { phase in
if preferredAction == .showContextualPalette, case let .ended(value) = phase {
if let anchorPoint = value.hoverPose?.anchor {
contextualPaletteAnchor = .point(anchorPoint)
}
contextualPalettePresented = true
}
}
}
@Environment(\.preferredPencilSqueezeAction)读取系统偏好(10:46).onPencilSqueeze修饰符接收 squeeze 事件,phase区分手势阶段value.hoverPose?.anchor获取悬停锚点,用于定位面板弹出位置
触觉反馈:UICanvasFeedbackGenerator
Apple Pencil Pro 的触觉反馈新增了两种画布专用类型:对齐辅助线(alignment)和路径完成(pathComplete)(11:36)。反馈 API 现在需要传入位置信息,让系统根据上下文选择最佳反馈方式。
UIKit 中使用:
class MyViewController: UIViewController {
@ViewLoading var feedbackGenerator: UICanvasFeedbackGenerator
override func viewDidLoad() {
super.viewDidLoad()
feedbackGenerator = UICanvasFeedbackGenerator(view: view)
}
func dragAlignedToGuide(_ sender: MyDragGesture) {
feedbackGenerator.alignmentOccurred(at: sender.location(in: view))
}
func snappedToShape(_ sender: MyDrawGesture) {
feedbackGenerator.pathCompleted(at: sender.location(in: view))
}
}
UICanvasFeedbackGenerator(view:)初始化时传入画布视图(11:50)alignmentOccurred(at:)在线条对齐辅助线时触发触觉反馈pathCompleted(at:)在形状识别完成、笔画吸附到规整形状时触发反馈
SwiftUI 中使用:
@State var dragAlignedToGuide = 0
@State var snappedToShape = 0
var body: some View {
MyView()
.sensoryFeedback(.alignment, trigger: dragAlignedToGuide)
.sensoryFeedback(.pathComplete, trigger: snappedToShape)
}
.sensoryFeedback(.alignment, trigger:)在 trigger 值变化时触发对齐反馈(12:29).sensoryFeedback(.pathComplete, trigger:)在 trigger 值变化时触发路径完成反馈- 将修饰符挂在最具体的子视图上,反馈更精准
Barrel roll 角度
Apple Pencil Pro 的 barrel roll 让转笔杆能控制笔触角度(08:04)。PencilKit 的 marker 和 fountain pen 已内置支持。自定义画布可以通过 UITouch.rollAngle 和 UIHoverGestureRecognizer.rollAngle 读取角度值。Apple 建议将 rollAngle 和 azimuth 角度相加使用,这样在不支持 roll 的设备上 azimuth 还能作为回退(13:32)。注意 rollAngle 初始是估算值,后续通过蓝牙更新,需要在 touchesEstimatedPropertiesUpdated 中获取最终精确值。
核心启发
-
做什么:把自定义绘制工具注册到 PKToolPickerCustomItem 中。为什么值得做:用户在系统工具栏里就能切换你的工具,squeeze 和 barrel roll 自动支持,不用自己实现交互逻辑。怎么开始:创建
PKToolPickerCustomItem.Configuration,定义颜色/宽度属性,提供图标渲染闭包,然后用toolItems初始化器构建工具栏。 -
做什么:在自定义画布中接入 squeeze 手势,弹出上下文工具面板。为什么值得做:用户不抬手就能切换工具或调出选项,绘制流程不中断。怎么开始:UIKit 中实现
UIPencilInteractionDelegate的pencilInteraction(_:didReceiveSqueeze:);SwiftUI 中使用.onPencilSqueeze修饰符。记得检查preferredSqueezeAction尊重系统设置。 -
做什么:在形状识别和对齐吸附时触发触觉反馈。为什么值得做:反馈让用户在视觉确认之前就能感知到吸附成功,手感更自然。怎么开始:UIKit 用
UICanvasFeedbackGenerator,调用alignmentOccurred(at:)和pathCompleted(at:);SwiftUI 用.sensoryFeedback(.alignment, trigger:)和.sensoryFeedback(.pathComplete, trigger:)。 -
做什么:在自定义画笔工具中读取 barrel roll 角度,控制笔触旋转。为什么值得做:模拟真实画笔的转笔效果,让数字绘画手感更接近纸笔。怎么开始:从
UITouch.rollAngle读取角度,加上azimuthAngle作为总旋转值,在touchesEstimatedPropertiesUpdated中更新最终精确值。
关联 Session
- Build a spatial drawing app with RealityKit — 用 RealityKit 从零构建空间绘画 App,涉及低层 mesh 和纹理 API
- What’s new in SwiftUI — SwiftUI 新增的 .onPencilSqueeze 和 .sensoryFeedback 修饰符的完整上下文
- What’s new in AppKit — macOS 端 hover 和指针交互的新进展
- Create custom hover effects in visionOS — visionOS 中自定义 hover 效果的实现方式
- Catch up on accessibility in SwiftUI — SwiftUI 无障碍元素与交互的辅助技术适配
评论
GitHub Issues · utterances