如何在 SwiftUI 预览(Preview)中模拟不同的环境(如暗黑模式、不同的设备尺寸、CoreData 数据注入)?
在 SwiftUI 中,利用 Xcode 的预览功能(Preview)可以极大地提高开发效率。从 Xcode 15 开始,Apple 引入了新的 #Preview 宏,使得语法更加简洁。
以下是如何在预览中模拟暗黑模式、不同设备尺寸以及注入 CoreData 数据的详细指南。
1. 模拟暗黑模式与亮色模式 (Dark Mode / Light Mode)
你可以通过 .preferredColorScheme 修饰符来强制视图在预览中显示为特定模式。
Xcode 15+ (#Preview 宏):
plaintext
#Preview("Light Mode") {
ContentView()
.preferredColorScheme(.light)
}
#Preview("Dark Mode") {
ContentView()
.preferredColorScheme(.dark)
}
旧版写法 (PreviewProvider):
plaintext
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.preferredColorScheme(.dark)
}
}
2. 模拟不同的设备尺寸与方向
你可以指定特定的设备型号,或者改变屏幕的方向(横屏/竖屏)。
指定设备型号
使用 .previewDevice 修饰符。
plaintext
#Preview("iPhone SE") {
ContentView()
.previewDevice(PreviewDevice(rawValue: "iPhone SE (3rd generation)"))
}
#Preview("iPad Pro") {
ContentView()
.previewDevice(PreviewDevice(rawValue: "iPad Pro (12.9-inch) (6th generation)"))
}
指定屏幕方向
使用 .previewInterfaceOrientation 修饰符。
plaintext
#Preview("Landscape") {
ContentView()
.previewInterfaceOrientation(.landscapeLeft)
}
动态字体大小 (Dynamic Type)
测试布局在不同字体大小时的表现非常重要:
plaintext
#Preview("Extra Large Text") {
ContentView()
.environment(\.sizeCategory, .extraExtraExtraLarge)
}
3. CoreData 数据注入 (最关键部分)
在预览中使用 CoreData 时,绝对不能使用生产环境的数据库(因为它会持久化到磁盘,导致预览崩溃或数据混乱)。你需要创建一个内存中 (In-Memory) 的 CoreData 栈,并预先填充假数据。
第一步:配置 PersistenceController
在你的 Persistence.swift 文件中,添加一个专门用于预览的静态属性。
plaintext
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
// 1. 定义一个专门给预览用的静态属性
static var preview: PersistenceController = {
// 注意这里传入 inMemory: true
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
// 2. 创建一些假数据
for i in 0..<10 {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
newItem.name = "Item \(i)"
}
// 3. 保存到内存存储中
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentContainer
// 初始化方法
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "MyModelName") // 替换为你的 .xcdatamodeld 文件名
if inMemory {
// 关键:指定存储路径为 /dev/null,即只在内存中运行
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
container.viewContext.automaticallyMergesChangesFromParent = true
}
}
第二步:在 Preview 中注入
使用 .environment(\.managedObjectContext, ...) 将刚才创建的内存数据库注入进去。
plaintext
import SwiftUI
import CoreData
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
var body: some View {
List {
ForEach(items) { item in
Text(item.name ?? "Unknown")
}
}
}
}
// MARK: - Preview 代码
#Preview {
// 注入包含假数据的内存 Context
ContentView()
.environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
4. 进阶技巧:组合预览 (Group Previews)
你可以将上述所有配置组合在一个文件中,一次性查看视图在不同环境下的表现。
plaintext
#Preview("Design System Check") {
Group {
// 场景 1: 默认状态
ContentView()
.previewDisplayName("Default")
// 场景 2: 暗黑模式 + 内存数据
ContentView()
.preferredColorScheme(.dark)
.environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
.previewDisplayName("Dark Mode with Data")
// 场景 3: 极小字体 + iPhone SE
ContentView()
.environment(\.sizeCategory, .extraSmall)
.previewDevice(PreviewDevice(rawValue: "iPhone SE (3rd generation)"))
.previewDisplayName("Small Device & Text")
}
}
总结
- 外观: 使用
.preferredColorScheme(.dark)。 - 设备: 使用
.previewDevice(...)和.previewInterfaceOrientation(...)。 - 数据: 必须使用
inMemory: true的 CoreData 容器,并通过.environment(\.managedObjectContext, ...)注入。