设计交互式 Snippet
Design interactive snippets
2025年6月9日
一句话判断
Snippet 从”系统替你弹一个结果卡片”变成了”你的 App 可以在系统层做轻量级交互”——按钮、数据更新、确认流程全支持。设计指南的核心矛盾是:既要信息密度够高,又要一眼就能扫完。
这场 Session 讲了什么
这是一篇纯粹的设计指南,讲的是交互式 Snippet 在视觉和交互设计上的最佳实践。
外观方面,Snippet 用比系统默认更大的字号——目的是让用户在瞬间抓到关键信息。布局要留够内边距,可以用 ContainerRelativeShape API 确保跨平台一致性。高度限制在 340 points 以内,超出就需要滚动,破坏了”瞥一眼就懂”的体验。如果需要更多信息,Snippet 里加按钮跳转到 App 内完整视图。背景可以使用基于 App 视觉身份的渐变色,但要特别注意对比度——尤其是远距离查看时(比如 StandBy 模式)。
交互方面,Snippet 支持按钮和 Toggle。按钮直接关联已有的 App Intent,不需要额外代码。数据更新用 scale + blur 动画提供视觉反馈,让用户确认操作已生效。可以在 Snippet 里同时放多个按钮和多段更新数据——比如均衡器预设。
Snippet 类型有两种。Result Snippet 展示最终结果,底部只有”Done”按钮——适合订单状态查询这类不需要后续操作的场景。Confirmation Snippet 在执行前要求用户确认,底部是可自定义的确认按钮(“Order""Send""Search”等)——适合需要用户做决策的场景。确认后可以接一个 Result Snippet 展示执行结果。
Dialog(Siri 语音播报)是可选的但很重要——戴耳机时用户看不到屏幕,需要通过语音获取信息。设计建议是 Snippet 本身要自洽——即使没有 dialog 也能理解意图。不要让 dialog 和 Snippet 重复传达同一信息。
值得深挖的点
”不需要 Dialog 也能自洽”的设计原则
这是一个容易被忽略但影响很大的设计决策。很多开发者倾向于在 dialog 里完整描述 Snippet 的内容,导致 Snippet 视图和 dialog 传达完全一样的信息。Session 的建议是反过来——先设计一个不需要任何语音辅助就能看懂的 Snippet,然后在这个基础上考虑 dialog 要补充什么。
具体做法:把 dialog 当作”盲人模式”的补充信息,而不是”正常模式”的完整描述。比如 Snippet 展示了一张天气图,dialog 不需要说”这是天气图”,而是说”明天有 60% 降雨概率,建议带伞”——这是图片传达不了的具体数字。
340 points 的高度限制
这个限制看似武断,但背后是 Snippet 在屏幕上的实际布局——Snippet 出现在屏幕顶部区域,下面是键盘或其他内容。如果超过 340 points,用户需要滚动才能看到全部内容,这和 Snippet 的”即时性”定位矛盾。
实际操作中,如果一个 Snippet 塞不下所有信息,说明这个场景不适合用 Snippet——考虑用 App 内的完整视图,或者用多个连续的 Snippet(确认 -> 执行中 -> 结果)来分步展示。
活泼背景的对比度问题
Snippet 支持用 App 品牌色做渐变背景,但 Session 特别强调了”远距离对比度”。标准的 WCAG 对比度比率是在屏幕前近距离查看的标准,Snippet 需要考虑用户可能在几米外(比如 Apple Watch 或 StandBy 模式)查看。建议把对比度阈值提高到比标准更严格的水平。
代码片段
Session 是纯设计指南,不提供代码。但结合 Session 275 的 API 知识,可以推导出以下设计模式对应的代码结构:
Result Snippet 模式
struct OrderStatusSnippet: SnippetIntent {
@Parameter var order: OrderEntity
func perform() async throws -> some IntentResult & ShowsSnippetView {
let status = await fetchOrderStatus(order)
return .result(view: OrderStatusView(
status: status,
estimatedDelivery: order.deliveryDate
))
// 底部自动显示 "Done" 按钮
}
}
Confirmation -> Result 流程
struct PlaceOrderIntent: AppIntent {
func perform() async throws -> some IntentResult & ShowsSnippetIntent {
// 请求确认,展示配置 Snippet
let quantity = try await requestConfirmation(
snippet: OrderConfirmationSnippet(items: cartItems),
dialog: "确认下单吗?"
)
let order = try await submitOrder(quantity: quantity)
// 确认后展示结果 Snippet
return .result(snippet: OrderResultSnippet(order: order))
}
}
最佳实践
Snippet 内容的原则:只展示和当前 Intent 直接相关的信息,不要试图把整个 App 的上下文塞进去。用户在 Snippet 里的注意力非常有限——他们可能正在用 Siri、正在 Spotlight 搜索、正在用手表——任何需要思考才能理解的内容都是噪音。
按钮设计:确认按钮的文案要用动作动词(“Order""Search""Delete”),不要用”OK”或”Confirm”——后者不能传达下一步会发生什么。确认按钮的视觉风格要和 Snippet 的重要性匹配——破坏性操作用红色,常规操作用默认样式。
数据更新的动画:scale + blur 是 Session 推荐的组合。不要用淡入淡出——在 Snippet 这种快速交互场景下,scale 变化更直接传达”数据已更新”。contentTransition API 和交互式 Widget 用的是同一套。
还有什么值得关注
- Snippet 支持的布局 API 和 SwiftUI View 完全一致——你可以用 VStack、HStack、ZStack 组织布局。
- ContainerRelativeShape API 确保 Snippet 内容的边距在不同设备和尺寸下都能正确适配。
- Snippet 里的按钮可以直接触发另一个 Snippet(通过返回 ShowsSnippetIntent 的 Intent),形成链式交互——但只有 Result 类型的 Snippet 会被替换,Confirmation 类型的不会。
- Snippet 的生命周期由系统管理——用户确认或取消后 Snippet 自动消失,不需要你手动关闭。