WWDC Quick Look 💓 By SwiftGGTeam
Build programmatic UI with Xcode Previews

Build programmatic UI with Xcode Previews

观看原视频

Highlight

Xcode 15 的 #Preview 宏支持 SwiftUI、UIKit、AppKit 和 Widget 预览,配合 Live/Selection/Variants 三种画布模式,开发者可以在不运行 App 的情况下实时迭代 UI,并直接在真机上预览。

核心内容

你写了一个 SwiftUI 视图,想看一下效果。以前要编译、启动、导航到对应页面。现在用 #Preview 宏,代码写在源文件顶层,Xcode 画布秒级刷新。

01:25)Preview 是一段编译进 App 的代码,可以访问项目中的所有符号和资源。修改 Swift 代码时,Xcode 只做最小增量编译,然后重新运行 preview。

基本用法

最简单的 preview 只需要一行:

#Preview {
    MyView()
}

05:05)如果视图通常放在 List 里,就在 preview 里放 List:

#Preview {
    List {
        CollageView(layout: .twoByTwoGrid)
    }
    .environment(CollageLayoutStore.sample)
}

关键点:

  • #Preview 写在源文件顶层,不嵌套在任何类型或函数里
  • 可以附加环境修饰符、数据依赖,和 App 的 scene 入口一样
  • 命名和配置 trait 作为可选参数传入

05:37)给 preview 加名字和方向:

#Preview("2x2 Grid", traits: .landscapeLeft) {
    List {
        CollageView(layout: .twoByTwoGrid)
    }
    .environment(CollageLayoutStore.sample)
}

UIKit 和 AppKit 支持

05:58#Preview 同样支持 UIKit:

#Preview {
    var controller = SavedCollagesController()
    controller.dataSource = CollagesDataStore.sample
    controller.layoutMode = .grid
    return controller
}

#Preview("Filter View") {
    var view = CollageFilterDisplayView()
    view.filter = .bloom(amount: 15.0)
    view.imageData = ...
    return view
}

关键点:

  • 返回 UIViewController 或 UIView 都可以
  • var 创建实例,配置属性,然后 return
  • trait 参数对 UIKit preview 同样有效

11:30)设置 UIKit preview 的初始方向:

#Preview("All Filters", traits: .landscapeLeft) {
    let viewController = FilterRenderingViewController()
    if let image = UIImage(named: "sample-001")?.cgImage {
        viewController.imageData = image
    }
    viewController.filter = Filter(
        bloomAmount: 1.0,
        vignetteAmount: 1.0,
        saturationAmount: 0.5
    )
    return viewController
}

三种画布模式

07:17)画布左下角有三种模式:

  1. Live 模式(默认):可交互,支持动画和异步代码。可以拖动滑块、测试动画。
  2. Selection 模式:静态快照。点击 UI 元素,Xcode 高亮对应的源码行。双击文本视图可以直接跳转编辑。
  3. Variants 模式:同时展示不同设备设置的变体,如所有配色方案或所有动态类型大小。

08:29)Device Settings 面板在画布底部,可以快速切换 dark mode、动态类型大小,不需要修改代码。

Widget 预览

12:20)Widget preview 需要指定 widget family 和 timeline provider:

#Preview(as: .systemSmall) {
    FrameWidget()
} timelineProvider: {
    RandomCollageProvider()
}

关键点:

  • as: 参数指定 widget 尺寸(.systemSmall、.systemMedium 等)
  • timelineProvider: 提供时间线数据
  • Xcode 会快照每个时间线条目,可以用箭头键浏览
  • 点击播放按钮可以循环播放时间线过渡动画

13:28)如果想测试特定的过渡场景,用 timeline 代替 timelineProvider

#Preview(as: .systemSmall) {
    FrameWidget()
} timeline: {
    TimelineEntry(date: .now, collage: collageA)
    TimelineEntry(date: .now.addingTimeInterval(3600), collage: collageB)
}

Pin 和循环播放

13:58)点击画布左上角的 pin 按钮,可以在切换到其他文件时保持 preview 活跃。配合循环播放按钮,可以持续观察某个动画过渡效果,同时修改代码。

详细内容

在 Library Target 中使用 Preview

16:15)Preview 需要可执行文件(App 或 Widget)来运行。Xcode 通过以下规则选择:

  1. 当前编辑的源文件所属 target
  2. 这些 target 的所有依赖
  3. 与当前 scheme 的交集

如果没有 App,Xcode 会自动创建 XCPreviewAgent。如果 view 需要特定 Info.plist 键(如相册权限),可以创建一个专门的 preview app target,添加所需权限,然后把 library 嵌入进去。

Development Assets

20:30)Preview 用的图片等资源可以放在 Development Assets 文件夹中。这些资源在开发时使用,提交 App Store 时自动移除。

设置方式:Project Settings > Build Settings > Development Assets,添加文件夹路径。新建项目时默认已有一个 Preview Content 文件夹。

真机预览

23:25)画布底部的设备选择器可以选择连接的真机。选择后 Xcode 直接为真机构建和预览,绕过模拟器。

真机预览的优势:

  • 访问相机、传感器等硬件
  • 使用设备上的真实数据(如照片库)
  • 最高保真度的 UI 验证

25:07)修改代码后,真机上的 preview 即时更新:

.navigationTitle("Add Collage")

加一行代码,真机立刻显示新标题。

核心启发

  • 做什么:给项目中每个自定义视图加 #Preview

  • 为什么值得做:修改视图代码后秒级看到效果,不需要编译运行整个 App。UIKit 项目也能享受这个工作流

  • 怎么开始:在视图源文件顶层加 #Preview { YourView() },确保画布模式已开启

  • 做什么:用 Variants 模式检查所有动态类型大小下的布局

  • 为什么值得做:Accessibility 要求支持最大字体。Variants 模式一次性展示所有尺寸,快速发现截断或重叠问题

  • 怎么开始:点击画布 Variants 按钮,选择 Dynamic Type,检查每个变体的布局

  • 做什么:给 Widget 加 timeline preview,测试时间线过渡动画

  • 为什么值得做:Widget 的动画只在时间线条目切换时发生,平时很难测试。Preview 可以循环播放这些过渡

  • 怎么开始:用 #Preview(as: .systemSmall) { MyWidget() } timelineProvider: { MyProvider() }

  • 做什么:用真机预览测试依赖设备数据的视图

  • 为什么值得做:模拟器没有真实照片、联系人、健康数据。真机 preview 可以直接用这些真实数据验证

  • 怎么开始:用设备选择器选连接的真机,确保 Development Assets 里有需要的测试资源

关联 Session

评论

GitHub Issues · utterances