WWDC Quick Look 💓 By SwiftGGTeam
Build SwiftUI views for widgets

Build SwiftUI views for widgets

观看原视频

Highlight

Apple 用 Wired 咖啡因小组件演示了 SwiftUI widget 视图的构建流程:VStack/ZStack 布局、动态时间文本、ContainerRelativeShape、placeholder 和 systemMedium 条件布局。

核心内容

这场 session 从一个很具体的需求开始。Wired App 记录用户喝过的含咖啡因饮料,并估算体内还剩多少咖啡因。作者想把这个状态放到 Home Screen,让用户一眼看到当前咖啡因量、上一杯饮料,以及距离上一杯过去了多久。

问题出在 Widget 的展示方式。它要延续 App 的颜色和视觉身份,要在小尺寸里保留重点,还要让底部的时间持续更新。圆角也不能随便写死,因为系统会给 Widget 决定形状、尺寸和 corner radius。

演示已经准备好了 timeline 配置,所以全部精力都放在视图层。SwiftUI 视图从 VStack 开始,逐步加入 ZStack 背景、HStack 对齐、Spacer 推开内容、默认 padding、asset catalog 颜色,以及按 Dark Mode 自动切换的颜色变体。

视图变复杂后,Apple 把顶部咖啡因数值抽成 CaffeineAmountView,把底部饮料信息抽成 DrinkView。这一步的作用很实际:让小组件继续扩展到 placeholder 和 systemMedium 尺寸时,代码还能保持清楚。

最后,medium widget 加入饮料照片,placeholder 直接复用同一套视图。SwiftUI 会把文本替换成圆角矩形,也会把图片替换成填充色。开发者要处理的是信息层级和条件布局,而不是重新写一套占位 UI。

详细内容

1. 用 preview 和数据结构收紧视图边界(02:23

演示一开始就把 widget 配置放到一边,只处理 SwiftUI 视图。preview 使用 WidgetPreviewContext 指定 systemSmall,视图数据来自 CaffeineWidgetData,其中包含咖啡因量、饮料名称和饮用时间。

WidgetPreviewContext
systemSmall
CaffeineWidgetData
VStack
ZStack
HStack
Spacer

关键点:

  • WidgetPreviewContext 用来让 preview 按指定 widget family 渲染。
  • systemSmall 是第一个目标尺寸,适合先把最核心的信息跑通。
  • CaffeineWidgetData 把 UI 需要的数据收在一起,preview 可以用固定样例让界面看起来接近真实状态。
  • VStack 负责上下排列咖啡因标题、数值、饮料名和时间。
  • ZStack 给内容背后加上背景色。
  • HStackSpacer 把内容推到 leading edge,避免小组件内容居中后失去信息层级。

2. 让时间文本自动更新(05:16

底部的“上一杯多久以前”不能停留在生成快照的那一刻。SwiftUI 的 Text 新 initializer 接受日期和样式,系统会根据时间变化自动更新显示内容。演示还用 string interpolation 加上 ago,框架会生成可本地化的 key。

// Displaying date and time

// June 3, 2019
Text(event.startDate, style: .date)

// 11:23PM
Text(event.startDate, style: .time)

// 9:30AM - 3:30PM
Text(event.startDate...event.endDate)

// +2 hours
// -3 months
Text(event.startDate, style: .offset)

// 2 hours, 23 minutes – Automatically updating as time pass
Text(event.startDate, style: .relative)

// 36:59:01 – Automatically updating as time pass
Text(event.startDate, style: .timer)

关键点:

  • .date.time 用系统格式展示绝对日期和时间。
  • date range 可以直接展示一段时间,例如活动开始到结束。
  • .offset 适合显示相对偏移量。
  • .relative 会随时间流逝更新,正好对应“上一杯多久以前”。
  • .timer 适合倒计时或计时器类信息。

3. 用 ContainerRelativeShape 处理同心圆角(18:40

咖啡因数值的背景需要和 widget 外框保持同心圆角。手写 cornerRadius 会遇到设备差异,因为不同设备和配置可能使用不同的 widget 圆角。iOS 14 的 ContainerRelativeShape 会读取最近的容器形状,并根据当前位置给内部 shape 计算合适的圆角。

// Concentric corner radius with ContainerRelativeShape

struct PillView : View {
    var title: Text
    var color: Color

    var body: some View {
        Text(title)
            .background(ContainerRelativeShape().fill(color))
    }
}

关键点:

  • PillView 把带背景的文本抽成一个可复用视图。
  • ContainerRelativeShape() 使用父容器提供的形状信息。
  • .fill(color) 把这个相对形状填成指定颜色。
  • .background(...) 让背景跟随文本视图,同时保持和 widget 外框同心。
  • 当外层 padding 改变时,内部圆角也会调整,边框厚度在曲线上保持一致。

4. placeholder 和尺寸家族共用同一套视图(14:24

Widget 需要 placeholder,因为系统可能在请求 extension 视图时使用它,也可能在设备锁定时使用它。演示给 preview 加上 isPlaceholder(true),SwiftUI 自动把文本内容替换成匹配原内容的圆角矩形。如果某些文字必须保留,例如固定的 caffeine 标签,可以给那段内容加 isPlaceholder(false)

.isPlaceholder(true)
.isPlaceholder(false)
systemMedium

关键点:

  • .isPlaceholder(true) 让整棵视图进入 placeholder 样式。
  • .isPlaceholder(false) 可以保留某一段固定文本。
  • systemMedium preview 用来检查中尺寸 widget 的信息密度。
  • medium 布局里,演示通过 widget family 条件判断,只在 systemMedium 且有照片时展示图片。
  • placeholder 也会处理图片,把图片内容替换成 fill color。

核心启发

1. 做一个“当前状态”小组件

做什么:把 App 里最常被查看的一个数字放到 small widget,比如咖啡因量、预算余额、今日步数或设备电量。

为什么值得做:Wired 的例子证明,小尺寸只要抓住一个核心状态,就能减少用户打开 App 的次数。

怎么开始:先定义一个只包含核心数值、名称和时间的数据结构,再用 VStackSpacer 做出第一版 systemSmall preview。

2. 给最近事件加相对时间

做什么:显示“上一杯多久以前”“上次同步多久以前”“下一次活动还有多久”这类动态文本。

为什么值得做Text(event.startDate, style: .relative).timer 会随时间更新,适合让 Home Screen 上的信息保持活着。

怎么开始:从一条真实日期字段接入 Text 的日期样式,先用 .relative.timer 验证 preview 中的更新效果。

3. 把品牌色交给 asset catalog

做什么:为 widget 使用和 App 一致的颜色,并给这些颜色配置 Dark Mode 变体。

为什么值得做:演示中所有颜色来自 asset catalog,切到 Dark Mode 后 widget 自动适配,没有额外改布局代码。

怎么开始:把 widget 中使用的背景色和文字色放进 asset catalog,再用 preview 复制一份 Dark Mode 状态检查对比度。

4. 给 medium widget 加第二层信息

做什么:small 显示核心状态,medium 再加入图片或补充信息,例如最近一杯饮料照片、最近任务封面或日程地点。

为什么值得做:演示只在 systemMedium 中加入饮料照片,避免 small widget 被次要内容挤满。

怎么开始:先给数据结构增加图片名称,再通过 widget family 条件判断控制图片只出现在 medium 布局。

5. 直接复用正式视图做 placeholder

做什么:让 placeholder 和真实 widget 保持同一套结构,只隐藏具体数据。

为什么值得做isPlaceholder(true) 会把文本替换成圆角矩形,图片替换成填充色,用户看到的骨架和最终布局一致。

怎么开始:给 preview 加 placeholder 状态,再用 isPlaceholder(false) 保留固定标签或品牌词。

关联 Session

评论

GitHub Issues · utterances