WWDC Quick Look 💓 By SwiftGGTeam
Design widgets for the Smart Stack on Apple Watch

Design widgets for the Smart Stack on Apple Watch

观看原视频

Highlight

watchOS 10 引入 Smart Stack,让 widget 可以直接覆盖在表盘上方。开发者通过六种标准布局、自定义配色和基于信号的 relevancy 机制,能让用户抬腕 10 秒内获取最关键的信息。

核心内容

从表盘到 Smart Stack:信息获取方式的进化

Apple Watch 的核心价值是抬腕即见(glanceable information)。以前,复杂功能(complications)只能嵌入特定表盘,像 Wayfinder、Modular 这类表盘虽然信息密度高,但牺牲了个性化表达的空间。

watchOS 10 的 Smart Stack 解决了这个矛盾。无论用户选择什么表盘,转动数码表冠就能呼出一个按相关性智能排序的 widget 堆栈。用户还可以手动添加、删除或置顶 widget,让堆栈既动态又可控。

00:14

六种标准布局:保持一致性的设计语言

widget 的阅读体验像翻书,每页的风格、位置、字号应该一致。Apple 为此定义了六种标准布局:

三行文本布局 — 适合纯文本信息。News widget 用它展示标题和摘要。

带颜色编码的文本布局 — 在文本基础上加入颜色区分。Calendar widget 用它同时显示事件内容和日历颜色。

条形进度图布局 — 图形内容配辅助文字。Audiobook widget 用它展示阅读进度和当前章节。

圆形图形布局 — 适合环形数据展示。Activity widget 的健身环就是这个布局,文字补充卡路里、运动时长等细节。

大文本布局 — 适合单个数值或关键词。Calendar Month widget 用它显示日期,你也可以用它展示状态词如”高”或”低”。

图表布局 — 适合时间序列数据。

02:49

设计 widget 时只展示必要信息。用户在 Watch 上浏览 glanceable 信息的时间不应超过 10 秒。

如果标准布局无法满足需求,可以自定义布局,但仍建议使用系统文本样式类保证可读性。

颜色与图标:让 widget 一眼可识别

Smart Stack 中的 widget 不再受表盘色调限制,开发者可以更自由地使用品牌色或信息色。

默认情况下 widget 使用深色材质背景配白色文字。Apple 鼓励开发者选择有辨识度的背景色:Weather widget 用动态渐变传达天气状况,Stocks widget 用红绿区分涨跌,Audiobook widget 用模糊的书封面营造氛围。

图标方面,建议使用 SF Symbol 或矢量图标,与文字搭配更协调。

07:02

Session 与 widget 的配合

Session 是应用中有明确起止时间的活跃状态,比如播放音乐、计时器运行、健身记录中。

Smart Stack 会自动在顶部生成系统级的 Session Control widget。因此,如果你的 widget 与 Session 相关(如音乐或健身),应该聚焦于 Session 前后的辅助信息,比如推荐一首歌或展示今日训练计划。这样既避免了功能重复,又能在 Session 期间保持 widget 的实用性。

08:41

Relevancy:让 widget 在最需要的时候出现

Smart Stack 的核心能力是按相关性智能排序。以下是五个值得利用的信号时刻:

  • 时间和日期临近 — Calendar widget 在事件开始前一小时提升优先级
  • 到达特定位置 — Reminders 用 GPS 或推断位置(家、公司、学校)触发
  • 检测到耳机 — Audiobooks 在 AirPods 连接时置顶
  • 起床或就寝时间 — 结合睡眠数据
  • 开始或结束运动 — Activity 在有活跃 Workout 时提升排序

10:01

详细内容

Smart Stack 的交互模型

Smart Stack 的交互由两个核心行为驱动:

  1. 系统智能排序:基于时间、位置、设备状态等信号自动调整 widget 顺序
  2. 用户手动控制:添加、删除、置顶 widget

Combo widget 是每个 Smart Stack 的默认组件,支持三个圆形复杂功能。它适合放置圆形应用启动器,但更好的用法是让三个复杂功能形成信息组合,比如同时显示温度、紫外线指数和空气质量。

设计实践要点

widget 设计需要回答三个问题:

  • 布局:内容适合哪种标准布局?是否需要自定义?
  • 识别度:背景色和图标能否让用户一眼认出你的应用?
  • 时机:什么信号能让你的 widget 在最需要的时候浮到顶部?

代码示例:WidgetKit 基础结构

虽然本 session 是设计导向,没有提供代码片段,但实现 Smart Stack widget 需要以下 WidgetKit 基础结构:

import WidgetKit
import SwiftUI

struct SmartStackWidget: Widget {
    let kind: String = "SmartStackWidget"

    var body: some WidgetConfiguration {
        StaticConfiguration(kind: kind, provider: Provider()) { entry in
            SmartStackWidgetView(entry: entry)
        }
        .configurationDisplayName("My App Widget")
        .description("Glanceable info for Smart Stack")
        .supportedFamilies([.accessoryRectangular])
    }
}

struct Provider: TimelineProvider {
    func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []
        let currentDate = Date()
        
        // 根据 relevancy 信号生成时间线
        for hourOffset in 0 ..< 5 {
            let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
            let entry = SimpleEntry(date: entryDate, relevance: nil)
            entries.append(entry)
        }
        
        let timeline = Timeline(entries: entries, policy: .atEnd)
        completion(timeline)
    }
}

struct SimpleEntry: TimelineEntry {
    let date: Date
    let relevance: TimelineEntryRelevance?
}

关键点:

  • supportedFamilies 设置为 .accessoryRectangular,这是 Smart Stack 支持的 widget 尺寸
  • TimelineEntryRelevance 用于控制 widget 在堆栈中的排序优先级
  • Provider 的 getTimeline 方法根据业务逻辑预生成多个时间点的内容

使用 Relevancy 提升 widget 优先级

import WidgetKit

struct RelevantProvider: TimelineProvider {
    func getTimeline(in context: Context, completion: @escaping (Timeline<RelevantEntry>) -> ()) {
        let now = Date()
        
        // 模拟一个即将开始的事件
        let eventStart = Calendar.current.date(byAdding: .minute, value: 30, to: now)!
        
        let entry = RelevantEntry(
            date: now,
            relevance: TimelineEntryRelevance(
                score: 100,  // 分数越高,排序越靠前
                duration: 3600  // 优先级持续 1 小时
            )
        )
        
        let timeline = Timeline(entries: [entry], policy: .after(eventStart))
        completion(timeline)
    }
}

struct RelevantEntry: TimelineEntry {
    let date: Date
    let relevance: TimelineEntryRelevance?
}

关键点:

  • TimelineEntryRelevancescore 决定 widget 在堆栈中的相对位置
  • duration 指定该优先级持续的时间窗口
  • 结合日历事件、位置变化或耳机连接等信号动态调整 score

核心启发

  • 做一个”会议前提醒”widget

    • 做什么:在会议开始前 15 分钟自动浮到 Smart Stack 顶部,显示会议室位置和参会人
    • 为什么值得做:利用时间 relevancy 信号,解决”快到会议了才想起来看日程”的痛点
    • 怎么开始:用 TimelineEntryRelevance(score:duration:) 在事件前设置高优先级,widget 用三行文本布局展示会议标题、时间、地点
  • 做一个”到家自动切换”widget

    • 做什么:用户到达家附近时,widget 置顶显示智能家居快捷控制
    • 为什么值得做:利用位置 relevancy,把”打开 Home app → 找到设备 → 操作”的三步流程压缩成抬腕一瞥
    • 怎么开始:用 CLRegion 定义家的地理围栏,在 getTimeline 中根据位置状态返回不同 relevance score
  • 做一个”运动后数据”widget

    • 做什么:Workout 结束后自动显示本次运动的摘要(时长、消耗、心率区间)
    • 为什么值得做:与系统 Session Control widget 互补,在用户最需要回顾数据的时刻出现
    • 怎么开始:监听 HKWorkoutSession 状态变化,在 workout 结束时通过 WidgetCenter.shared.reloadTimelines(ofKind:) 触发 widget 刷新并设置高 relevance
  • 做一个”耳机连接推荐”widget

    • 做什么:检测到耳机连接时,置顶推荐最近没听完的播客或歌单
    • 为什么值得做:Audiobooks 的同款思路,把”想听什么”的决策成本降到零
    • 怎么开始:用 AVAudioSession 监听路由变化,在耳机连接时通过 relevance 机制提升 widget 排序

关联 Session

评论

GitHub Issues · utterances