基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

如何在 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")
    }
}

总结

  1. 外观: 使用 .preferredColorScheme(.dark)
  2. 设备: 使用 .previewDevice(...).previewInterfaceOrientation(...)
  3. 数据: 必须使用 inMemory: true 的 CoreData 容器,并通过 .environment(\.managedObjectContext, ...) 注入。
00:00
00:00