WWDC Quick Look 💓 By SwiftGGTeam
Build complications in SwiftUI

Build complications in SwiftUI

观看原视频

Highlight

watchOS 7 为 Graphic Corner、Circular、Rectangular 和 Extra Large Complication 增加可承载 SwiftUI 视图的 ClockKit 模板,并提供 Text 自动更新、ProgressView、Gauge、表盘 tint 处理和 Xcode previews 支持。

核心内容

Apple Watch 的 Complication 负责在表盘上显示及时、相关的信息。Session 开头用 UVI complication 举例:一个很小的区域,可以用带颜色渐变的垂直 gauge 告诉用户今天是否需要防晒(00:22)。

难点在于,好的 Complication 要把复杂信息压缩到抬腕一眼能看懂的形态。过去主要依赖 ClockKit 预定义 template 和 provider;watchOS 7 增加了可以接收 SwiftUI view 的 template,覆盖 Graphic Corner、Circular、Rectangular 和 Extra Large families(01:29)。

SwiftUI 在这里给开发者一个更自由的画布。Session 特别提到 GraphicRectangularFullView 是新的 template,给 Rectangular family 提供更大的绘制区域。Dawn Patrol 的潮汐图和 Coffee Tracker 的周咖啡摄入图,都是把图表类信息放到表盘上的例子(02:0313:48)。

这个自由度也有边界。Complication 是 timeline 里的静态 view,点击任意位置都会打开 App;按钮、手势、其他交互元素和 SwiftUI animation 都不支持。系统还会在 view 显示前测量性能,复杂或尺寸不合适的 view 会触发 runtime warning(19:12)。

详细内容

把 SwiftUI view 放进 ClockKit template

01:29)ClockKit 新增的 Graphic templates 可以接收 SwiftUI view。下面这个官方片段同时展示了 SwiftUI view、相对时间文本,以及用 previewContext() 在 Xcode 里预览 complication。

import SwiftUI
import ClockKit

struct RelativeText: View {
    var body: some View {
        VStack(alignment: .leading) {
            Text("Count Down")
                .font(.headline)
                .foregroundColor(.accentColor)
            Label("Nap Time", systemImage: "moon.fill")
            Text(Date() + 100, style: .relative)
        }
        .frame(maxWidth: .infinity, alignment: .leading)
    }
}

struct RelativeText_Previews: PreviewProvider {
    static var previews: some View {
        CLKComplicationTemplateGraphicRectangularFullView(RelativeText())
            .previewContext()
    }
}

关键点:

  • import SwiftUIimport ClockKit 同时出现,previewContext() 才能把 template 包成 SwiftUI preview 可识别的 view。
  • RelativeText 只是普通 SwiftUI View,可以用 VStackTextLabel 和 modifier 组织内容。
  • Text(Date() + 100, style: .relative) 使用 session 点名的 relative date formatter,表盘会自动保持相对时间准确。
  • CLKComplicationTemplateGraphicRectangularFullView(RelativeText()) 把 SwiftUI view 交给 Graphic Rectangular Full View template。
  • .previewContext() 让 Xcode previews 把这个 template 放到适合该 family 的表盘上。

用 Text、ProgressView 和 Gauge 表达一眼可读的信息

02:38)SwiftUI 的 Text 会感知所在 complication family:默认字号会随 family 改变,默认字体也改为 SF Rounded。Session 还点名 relative、offset 和 timer styles,它们会由表盘自动更新。

import SwiftUI
import ClockKit

struct TimerText: View {
    var body: some View {
        VStack(alignment: .leading) {
            Label("Sourdough Timer", systemImage: "timer")
                .foregroundColor(.orange)
            Text("Time remaining: \(Date() + 100, style: .timer)")
        }
        .frame(maxWidth: .infinity, alignment: .leading)
    }
}

struct TimerText_Previews: PreviewProvider {
    static var previews: some View {
        CLKComplicationTemplateGraphicRectangularFullView(TimerText())
            .previewContext()
    }
}

关键点:

  • Label("Sourdough Timer", systemImage: "timer") 用 SF Symbol 和短文字说明这个 complication 的用途。
  • .foregroundColor(.orange) 给标题一个明确状态色;后面的 tinting 规则会决定它在表盘着色时如何变化。
  • Text("Time remaining: \(Date() + 100, style: .timer)") 把 timer style 放进字符串插值,表盘会显示剩余时间。
  • .frame(maxWidth: .infinity, alignment: .leading) 让内容占满 Rectangular template 的可用宽度,并靠左排列。

03:37)ProgressView 适合线性推进的信息,例如音乐进度;Gauge 适合随时间变化的值,例如温度或酸碱度。

import SwiftUI
import ClockKit

struct ProgressSample: View {
    var body: some View {
        VStack(alignment: .leading) {
            Text("Water Reminder")
                .foregroundColor(.blue)
            Text("32 oz. consumed")
            ProgressView(value: 0.7)
                .progressViewStyle(LinearProgressViewStyle(tint: .blue))
        }

    }
}

struct ProgressSample_Previews: PreviewProvider {
    static var previews: some View {
        CLKComplicationTemplateGraphicRectangularFullView(ProgressSample())
            .previewContext()
    }
}

关键点:

  • Text("Water Reminder") 给用户一个标题,避免只有进度条时缺少语境。
  • Text("32 oz. consumed") 把当前状态写成短句,适合 Rectangular family 的横向空间。
  • ProgressView(value: 0.7) 表示一个 0 到 1 的进度值。
  • LinearProgressViewStyle(tint: .blue) 使用 session 中展示的线性样式和 tint。
  • Preview 仍然通过 CLKComplicationTemplateGraphicRectangularFullViewpreviewContext() 完成。
import SwiftUI
import ClockKit

struct GaugeSample: View {
    @State var acidity = 5.8

    var body: some View {
        Gauge(value: acidity, in: 3...10) {
            Image(systemName: "drop.fill")
                .foregroundColor(.green)
        } currentValueLabel: {
            Text("\(acidity, specifier: "%.1f")")
        } minimumValueLabel: {
            Text("3")
        } maximumValueLabel: {
            Text("10")
        }
        .gaugeStyle(
            CircularGaugeStyle(
                tint: Gradient(colors: [.orange, .yellow, .green, .blue, .purple])
            )
        )
    }
}

struct GaugeSample_Previews: PreviewProvider {
    static var previews: some View {
        CLKComplicationTemplateGraphicCircularView(GaugeSample())
            .previewContext()
    }
}

关键点:

  • @State var acidity = 5.8 给 Gauge 一个当前值,示例来自 session 的 garden soil acidity 场景。
  • Gauge(value: acidity, in: 3...10) 定义数值范围,session 中的范围是 3 到 10。
  • currentValueLabel 在 Gauge 中央显示当前值,帮助用户快速读数。
  • minimumValueLabelmaximumValueLabel 给起止值加标签。
  • CircularGaugeStyle(tint: Gradient(...)) 使用 gradient tint,让不同区间有不同颜色提示。
  • CLKComplicationTemplateGraphicCircularView 对应 Circular family 的预览。

处理表盘 tint

06:03)表盘支持多种 tint color,Complication 会跟随表盘颜色变化。Session 把 tinting 分成两种效果:desaturated tint 和 color opacity tint。

默认行为是 desaturated tint。表盘着色时,系统会创建 view 的灰度版本;部分表盘还会在灰度 view 上叠加单一颜色。Session 用一个黄色苹果和蓝色圆形说明:如果两个颜色亮度接近,desaturated 后苹果可能会消失,所以设计时要检查灰度状态(07:03)。

color opacity tint 需要开发者把 view hierarchy 分成两层。Transcript 点名的关键 API 是 complicationForeground modifier:默认内容位于 background layer,应用该 modifier 的内容会进入 foreground layer。表盘随后只看每层的 opacity,并由具体表盘决定给哪一层上色(08:3210:12)。

更细的控制来自 ComplicationRenderingMode。它有 fullColortinted 两个值,可以从 environment 读取。Session 的例子是在 full-color 模式保留蓝色 fill,在 tinted 模式改成只改变 opacity 的 linear gradient,因为 color opacity tint 最终只使用 opacity(11:32)。

关键点:

  • 默认 tinting 是 desaturated tint,先检查 view 在灰度下是否仍然可读。
  • complicationForeground 用来把 view 的一部分提升到 foreground layer。
  • color opacity tint 只使用 opacity,原始颜色不会直接保留。
  • 不同表盘可能给 foreground 和 background layer 应用不同颜色。
  • ComplicationRenderingMode 允许同一个 view 针对 fullColortinted 分支调整渲染。

用 Xcode previews 检查复杂 complication

13:42)Coffee Tracker 示例把 App 内的 HistoryView 和新的 ComplicationHistoryView 放在 preview 里并排查看,目的是避免改 complication 时误伤 App 内页面。复杂图表可以单独拆成 complication 专用 view,让表盘尺寸、字体和 tinting 调整集中在一个地方。

previewContext() 是这段工作流的入口。它定义在 CLKComplicationTemplate 上,并且需要同时 import ClockKit 和 SwiftUI。它会把 template 包装成 SwiftUI view,让 previews 把 complication 放到适合该 family 的表盘上(15:11)。

Session 还展示了 PreviewFaceColors:可以枚举不同表盘颜色,把 multicolor face 和 tinted face 放在一起看。这样能在修改另一个文件时实时观察 complication 如何随 tint 更新(18:16)。

关键点:

  • 简单 view 可能只需要少量属性调整就能进入 complication。
  • 复杂图表适合拆出单独的 complication view。
  • previewContext() 让 template 进入 SwiftUI previews。
  • PreviewFaceColors 用来同时检查多个表盘颜色。
  • 预览要覆盖 full color 和 tinted 两种状态。

发布前检查限制和布局边界

19:12)Complication 不是一个迷你 App。点击任何区域都会打开 App;Buttons、gestures 和其他 interactive elements 不受支持。Session 建议只使用 text、images 和 drawing primitives。

SwiftUI animation 也不受支持,因为 Complication 是由静态 view 组成的 timeline。系统会在显示前测量每个 SwiftUI view 的性能。Session 直接建议:如果 Xcode 对尺寸或复杂度给出 runtime warning,要把它当成 build error 修掉再发布(19:38)。

布局上,Circular 和 Rectangular families 会把内容 mask 到对应形状。Rectangular Full View 有 safe area,防止内容在某些表盘位置被 Apple Watch 屏幕边角裁掉。如果确实要使用完整区域,可以用 edgesIgnoringSafeArea,但要确认内容没有被裁切(20:46)。

关键点:

  • Complication tap 统一打开 App,不能依赖局部按钮或手势。
  • Timeline view 是静态内容,SwiftUI animation 不会在 complication 中工作。
  • 图片尺寸、blur、formatted text 等绘制成本会影响运行时表现。
  • Runtime warning 要在发布前修掉。
  • Circular 和 Rectangular families 会 mask 内容;Full View 的 safe area 默认保护所有位置。

核心启发

  • 做一个倒计时类 Complication:例如午睡、烘焙、会议前提醒。Session 的 Text(Date() + 100, style: .relative) 和 timer style 会由表盘自动更新,起步方式是用 CLKComplicationTemplateGraphicRectangularFullView 承载一个短标题、SF Symbol 和时间文本。
  • 做一个进度类 Complication:例如饮水量、音乐播放、任务完成度。ProgressView 的 circular 和 linear styles 已经适配小尺寸表盘,起步方式是先用 ProgressView(value:) 表示 0 到 1 的状态,再按 family 选择 circular 或 linear style。
  • 做一个区间读数 Complication:例如土壤酸碱度、紫外线、温度。Gauge 支持当前值、最小值、最大值和 gradient tint,起步方式是先确定数值范围,再用 Gauge(value:in:)currentValueLabel 让用户一眼看到读数。
  • 把复杂图表拆成 complication 专用 view:Coffee Tracker 示例把周咖啡摄入图抽成 ComplicationHistoryView。起步方式是把 App 内的大图表缩成只保留标题和关键柱状或 gauge 的 SwiftUI view,然后用 previewContext() 同时看 App view 和 complication view。
  • 建立 tinting 预览清单:表盘 tint 会改变颜色和层级表现。起步方式是用 complicationForeground 标记 foreground layer,用 ComplicationRenderingMode 分别处理 fullColortinted,再用 PreviewFaceColors 检查多个表盘颜色。

关联 Session

评论

GitHub Issues · utterances