WWDC Quick Look 💓 By SwiftGGTeam
Add rich graphics to your SwiftUI app

Add rich graphics to your SwiftUI app

观看原视频

Highlight

SwiftUI 在 iOS 15 中新增了 CanvasTimelineView、Materials 和 foreground styles 等 API,让开发者可以用声明式语法实现高性能自定义图形、粒子动画和沉浸式视觉效果。

核心内容

做图形类 App 时,开发者经常面临一个矛盾:想要全屏沉浸的视觉体验,又担心内容被刘海、Home 指示条或键盘遮挡。

以前的做法是手动计算各种 insets,用 GeometryReader 读取安全区域,再调整布局。代码冗长,不同设备表现还不一致。

WWDC2021 的 SwiftUI 带来了几个关键改进,让这个痛点迎刃而解。

Safe Area 的精细化控制

ignoresSafeArea() 修饰符现在可以指定忽略哪些安全区域。比如背景图想要全屏铺满,但内容仍要避开键盘:

ContentView()
    .ignoresSafeArea(.keyboard)

03:52

更强大的是 safeAreaInset 修饰符。它允许你在某个边缘添加辅助内容(比如底部工具栏),同时自动让主内容的安全区域内缩,避免被遮挡。

List {
    // 主内容
}
.safeAreaInset(edge: .bottom) {
    // 底部工具栏
    HStack {
        Button("Cancel") {}
        Spacer()
        Button("Save") {}
    }
    .padding()
    .background(.thinMaterial)
}

09:03

Materials 和 Vibrancy

iOS 15 引入了 Material 模糊效果,从 ultraThinultraThick 共 6 个级别。配合 .foregroundStyle(.secondary) 等层级样式,文字会自动获得 Vibrancy 效果——根据背景色动态调整对比度。

Text("Hello")
    .foregroundStyle(.secondary)
    .background(.ultraThinMaterial)

06:13

Canvas 和 TimelineView

对于需要绘制大量图形元素的场景(比如粒子系统、数据可视化),Canvas 提供了直接绘制的能力,性能远超组合 View

TimelineView 则让动画更新可控——你可以用 .animation schedule 获得类似 CADisplayLink 的逐帧回调。

详细内容

前景样式的新体系

foregroundStyle 取代了旧的 foregroundColor,支持更丰富的语义层级:

VStack {
    Text("Primary")
        .foregroundStyle(.primary)
    Text("Secondary")
        .foregroundStyle(.secondary)
    Text("Tertiary")
        .foregroundStyle(.tertiary)
    Text("Quaternary")
        .foregroundStyle(.quaternary)
}
.foregroundStyle(.purple)

07:08

关键点:

  • .primary.quaternary 是语义层级,在 Material 上下文中自动获得 Vibrancy 效果
  • 外层 .foregroundStyle(.purple) 会把所有子视图的前景色调统一为紫色
  • 也可以传入 LinearGradient 等复杂样式
let blueGradient = LinearGradient(
    colors: [.blue, .teal],
    startPoint: .leading,
    endPoint: .trailing
)

VStack {
    Text("Primary").foregroundStyle(.primary)
    Text("Secondary").foregroundStyle(.secondary)
}
.foregroundStyle(blueGradient)

07:41

Canvas 绘制基础

Canvas 是一个专门的绘制视图,适合需要直接控制像素的场景:

Canvas { context, size in
    // 解析图片(避免每次绘制都重新解析)
    let resolvedImage = context.resolve(Image("sparkle"))

    // 获取图片尺寸
    let imageSize = resolvedImage.size

    // 在中心绘制
    let rect = CGRect(
        x: size.width / 2 - imageSize.width / 2,
        y: size.height / 2 - imageSize.height / 2,
        width: imageSize.width,
        height: imageSize.height
    )

    context.draw(resolvedImage, in: rect)
}

13:12

关键点:

  • context.resolve() 提前解析图片资源,提升性能
  • 闭包不是 ViewBuilder,可以用普通 for 循环
  • 所有绘制按顺序执行,后绘制的覆盖先绘制的

配合 TimelineView 做动画

TimelineView(.animation) { timeline in
    Canvas { context, size in
        let time = timeline.date.timeIntervalSinceReferenceDate
        let angle = Angle(radians: time.remainder(dividingBy: 3) * 2 * .pi / 3 * 2)
        let offsetX = cos(angle.radians) * 50

        let resolvedImage = context.resolve(Image("sparkle"))
        let imageSize = resolvedImage.size

        for i in 0..<particleCount {
            var contextCopy = context
            contextCopy.opacity = 0.7
            contextCopy.blendMode = .screen

            let rect = CGRect(
                x: size.width / 2 + offsetX - imageSize.width / 2,
                y: size.height / 2 + CGFloat(i) * 10 - imageSize.height / 2,
                width: imageSize.width,
                height: imageSize.height
            )
            contextCopy.draw(resolvedImage, in: rect)
        }
    }
}
.gesture(
    TapGesture()
        .onEnded { _ in
            particleCount += 1
        }
)

18:38

关键点:

  • TimelineView(.animation) 以屏幕刷新率触发更新
  • context 是值类型,复制后修改不影响原上下文
  • .blendMode(.screen) 让重叠部分变亮,适合发光效果
  • 可以在 Canvas 外层添加手势,但无法给内部单个元素加手势

无障碍支持

Canvas 是单张位图,内部元素对 Accessibility 不可见。需要手动添加描述:

Canvas { context, size in
    // 绘制代码
}
.accessibilityLabel("Particle animation showing \(particleCount) sparkles")
.accessibilityAddTraits(.isImage)

21:22

核心启发

1. 为照片/视频编辑 App 添加实时滤镜预览

用 Canvas 绘制滤镜后的图像,配合 TimelineView 实现 60fps 的实时预览。入口 API:Canvas + context.draw() + context.filter

2. 构建自定义图表和数据可视化

对于需要绘制数百个数据点的场景,Canvas 比组合 Shape 性能更好。可以结合 TimelineView 做动态数据流展示。入口 API:Canvas + context.fill(path, with: .color(...))

3. 实现游戏化的粒子效果

烟花、雪花、星光等效果用 Canvas + blendMode(.screen) 轻松实现。将粒子状态封装到 ObservableObject,TimelineView 驱动更新。入口 API:TimelineView(.animation) + @StateObject

4. 设计沉浸式阅读/创作界面

ignoresSafeArea(.keyboard) 让背景图全屏延伸,内容区域用 safeAreaInset 放置浮动工具栏,Material 做毛玻璃背景。入口 API:.background(Image(...).ignoresSafeArea()) + .safeAreaInset

5. 为 macOS App 添加 Vibrancy 侧边栏

SwiftUI 的 Material 在 macOS 上自动适配为原生 Vibrancy 效果。用 .listStyle(.sidebar) 配合 .foregroundStyle(.secondary) 即可获得系统级视觉质感。入口 API:.background(.ultraThinMaterial)

关联 Session

评论

GitHub Issues · utterances