PreviewProvider — 是 SwiftUI 协议,它定义了在 Xcode Canvas 中生成预览的入口点。实现该协议使开发者无需启动模拟器即可看到界面,加速了设计阶段的迭代。根据 Apple Developer Documentation(2026),如果项目使用 Canvas,则 PreviewProvider 对所有 SwiftUI View 都是必需的 — 没有它,Canvas 将无法显示用户界面。了解更多信息,请参阅 关于 SwiftUI 的文章。
要点
PreviewProvider — 是定义在 Xcode Canvas 中创建预览内容约定的 SwiftUI 协议。该协议包含一个必需属性:previews,类型为 some View。previews 返回的任何值都会在 Canvas 中显示为交互式预览。PreviewProvider 不需要继承 — 在 extension 中进行静态实现就足够了。
从架构上讲,PreviewProvider 不是 SwiftUI 运行时的一部分 — 它纯粹是一个开发工具。该协议使用 @available(iOS 13.0, *) 属性标记,不会在 release 版本中编译,因为 Xcode 使用条件编译将预览代码从生产版本中排除。这意味着 PreviewProvider 不会影响二进制文件大小和应用程序性能。
previews 属性 — PreviewProvider 的唯一要求。它必须返回任何 View:从简单的 Text 到包含 Group 和 ForEach 的复杂层次结构。Xcode 在 Canvas 中渲染返回的 View,应用系统设置(主题、大小、字体)。
import SwiftUI
struct GreetingView: View {
let name: String
var body: some View {
Text("你好,\(name)!")
.padding()
}
}
// PreviewProvider — 静态实现
struct GreetingView_Previews: PreviewProvider {
static var previews: some View {
GreetingView(name: "World")
}
}
命名约定:Apple 建议将预览结构命名为 {ViewName}_Previews。这不是编译器的强制要求,但可以提高可读性和项目导航。Xcode 在创建新的 SwiftUI 文件时会自动替换此模板。
工作机制 PreviewProvider 基于静态分发:Xcode 仅为 Debug 配置编译包含 PreviewProvider 的 extension,并在构建 Canvas 的过程中调用 previews。每次代码更改时,Xcode 只重新编译已更改的 PreviewProvider,从而确保近乎即时的预览更新。
SwiftUI 不保证预览与模拟器或设备上的最终 UI 完全匹配 — Canvas 使用简化的渲染。带有延迟的动画可能无法正确显示,并且某些 UIKit 组件(MapKit、WebView)在没有额外配置的情况下无法在 Canvas 中渲染。
Group 允许同时显示一个 View 的多个状态,从而加速不同配置设计时的迭代。Group 内的每个预览独立渲染。
struct ButtonView_Previews: PreviewProvider {
static var previews: some View {
Group {
ButtonView(title: "Primary", style: .primary)
.previewDisplayName("Primary")
ButtonView(title: "Disabled", style: .primary)
.disabled(true)
.previewDisplayName("Disabled")
ButtonView(title: "Secondary", style: .secondary)
.previewDisplayName("Secondary")
}
}
}
previewDisplayName 为 Canvas 中的每个预览添加标签,这在比较多个状态时特别有用。Group 中的最大预览数没有限制,但超过 6–8 个会减慢 Canvas 的速度。
Xcode 提供 多个修饰符用于配置预览显示。主要的有:previewDevice — 模拟特定设备(iPhone 16 Pro、iPad Air、Apple Watch Ultra),previewLayout — 设置大小(device、fixed、sizeThatFits)。这些修饰符的组合提供了对预览环境的完全控制。
previewDevice 接受包含设备名称的字符串,例如 “iPhone 16 Pro” 或 “iPad Pro 13-inch (M4)”。可用设备列表取决于 Xcode 中安装的模拟器。如果未找到设备,Canvas 会在默认设备上显示预览而不报错。
| 修饰符 | 描述 | 示例 |
|---|---|---|
| previewDevice | 设备模拟 | .previewDevice(“iPhone 16 Pro”) |
| previewLayout | 大小模式 | .previewLayout(.sizeThatFits) |
| previewDisplayName | 预览标签 | .previewDisplayName(“Dark Mode”) |
| preferredColorScheme | 设计主题 | .preferredColorScheme(.dark) |
| dynamicTypeSize | 字体大小 | .dynamicTypeSize(.xxxLarge) |
常见做法 — 同时在多个设备上显示一个 View 以检查响应性。为此,使用带有设备名称数组的 ForEach。
struct AdaptiveView_Previews: PreviewProvider {
static var previews: some View {
ForEach(["iPhone SE (3rd generation)", "iPhone 16 Pro Max", "iPad Pro 13-inch (M4)"], id: \.self) { device in
AdaptiveView()
.previewDevice(.previewDevice(device))
.previewDisplayName(device)
}
}
}
实际示例 展示了 PreviewProvider 的不同使用场景:从简单预览到具有实时数据和 UIKit 兼容性的复杂配置。
模拟数据 — 当 View 接受模型时预览的标准模式。用测试数据代替真实 API,可以在不启动应用程序的情况下直观检查 UI 状态。
struct UserProfileView: View {
let user: User
var body: some View {
VStack {
AsyncImage(url: user.avatarURL)
.clipShape(Circle())
Text(user.name)
.font(.title)
Text(user.bio)
.font(.body)
.foregroundColor(.secondary)
}
}
}
struct UserProfileView_Previews: PreviewProvider {
static var previews: some View {
UserProfileView(user: .mock)
.previewDisplayName("Profile")
UserProfileView(user: .mockLongName)
.previewDisplayName("Long Name")
}
}
UIKit 兼容性 — PreviewProvider 也适用于包装在 UIViewRepresentable 中的 UIKit 组件。这允许在 SwiftUI Canvas 中预览现有的 UIKit 视图,而无需迁移整个项目。
struct MapViewRepresentable: UIViewRepresentable {
func makeUIView(context: Context) -> MKMapView {
MKMapView()
}
func updateUIView(_ uiView: MKMapView, context: Context) {
// 配置地图
}
}
struct MapView_Previews: PreviewProvider {
static var previews: some View {
MapViewRepresentable()
}
}
Canvas — 是 Xcode 的可视化编辑器,实时渲染 PreviewProvider 的结果。没有 PreviewProvider 的实现,Canvas 将保持空白。Canvas 和 PreviewProvider 协同工作:PreviewProvider 确定显示什么,Canvas — 确定在哪里以及如何显示。
重要的是要理解:Canvas — 是预览的执行环境,而不是 PreviewProvider 的替代品。即使开发者没有打开 Canvas,PreviewProvider 也可以用于通过将鼠标悬停在 Canvas 图标上出现的预览来快速检查代码。根据 WWDC 2024,Apple 建议为每个 View 编写 PreviewProvider 作为开发标准,类似于编写单元测试。
| 组件 | 角色 | 必需性 |
|---|---|---|
| PreviewProvider | 确定预览内容 | Canvas 必需 |
| Canvas | 在编辑器中渲染预览 | 可选(可以使用 .preview) |
| SwiftUI View | UI 组件 | 必需 |
建议:为项目中每个公共 View 编写 PreviewProvider。这可以加速新开发人员的入职,简化代码审查,并允许在不构建整个项目的情况下快速检查视觉更改。
问题 1:预览不更新。如果 Canvas 未反映代码更改,原因通常是 DerivedData 缓存。通过 Product → Clean Build Folder(⇧⌘K)或手动删除 ~/Library/Developer/Xcode/DerivedData 文件夹来清理 DerivedData。清理后,Canvas 从头开始重建预览。
问题 2:PreviewProvider 看不到 @StateObject。PreviewProvider 创建 View 的静态实例,因此需要注入的依赖项(ViewModel、服务)必须通过初始化器或带有默认值的 @StateObject 传递。在预览中使用模拟对象而不是真实服务。
问题 3:动画在 Canvas 中不起作用。Canvas 不支持所有 SwiftUI 动画 — 特别是那些依赖于时间的动画(带延迟的 withAnimation、.spring)。要检查动画,请在模拟器上运行应用程序。Canvas 适用于静态布局检查。
依赖注入 — 使 PreviewProvider 与复杂 ViewModel 一起工作的最佳方法。创建一个带有测试数据的单独 ViewModel 实例,并将其传递给 View 的初始化器。
struct DashboardView: View {
@StateObject var viewModel: DashboardViewModel
var body: some View {
List(viewModel.items) { item in
Text(item.title)
}
}
}
struct DashboardView_Previews: PreviewProvider {
static var previews: some View {
DashboardView(viewModel: DashboardViewModel.mock)
}
}
模拟扩展:为 ViewModel 创建一个提供静态 .mock 实例的 extension。这使测试数据与 ViewModel 保持在一起,并使 PreviewProvider 易于阅读。
常见问题解答
从技术上讲不是 — 应用程序在没有 PreviewProvider 的情况下也能编译。但在实践中,Apple 和 SwiftUI 社区建议为每个公共 View 编写预览。PreviewProvider 可以加速开发,允许在不同设备上快速检查布局,并作为团队的视觉文档。
PreviewProvider 仅将代码添加到 Debug 版本,因此如果预览中使用了 release 配置中不可用的类型,则可能会出现编译错误。当使用 @available 与不支持 Canvas 的平台一起使用,或超出预览的复杂性限制时,也会出现错误。
直接传递 — 不可能,PreviewProvider 在隔离环境中运行。使用模拟数据:创建带有 .mock 实例的静态 model extension。对于带有 @StateObject 的 View,通过初始化器传递带有测试数据的 ViewModel。这可以在没有网络请求的情况下模拟真实数据。
不会,PreviewProvider 不影响 release 二进制文件的大小。Xcode 使用条件编译(#if DEBUG / #if !RELEASE)将预览代码从 release 版本中排除。PreviewProvider 代码仅存在于 Debug 配置中,不会进入 App Store 版本。
可以,Xcode 支持预览调试。在 previews 内部或 View 代码本身中设置断点,然后选择 Product → Preview → Debug Preview。之后,断点将在 Canvas 渲染时触发。这对于分析仅在预览中可见的布局问题非常有用。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。