如何在 SwiftUI 预览中预览核心数据数据

How to preview Core Data data inside SwiftUI Previews

这是我的演示(代码有点多,但我希望有人能看懂)。
我在 Core Data 中有一个名为 Activity 的实体,带有一个字符串字段。为此,我使用此扩展程序在预览中显示数据:

extension Activity {
    var _name: String {
        name ?? ""
    }
    
    static var example: Activity {
        let controller = DataController(inMemory: true)
        let viewContext = controller.container.viewContext

        let activity = Activity(context: viewContext)
        activity.name = "running"
        
        return activity
    }
}

为了设置核心数据,我使用了一个 DataController 对象:

class DataController: ObservableObject {
    let container: NSPersistentCloudKitContainer
    
    init(inMemory: Bool = false) {
        container = NSPersistentCloudKitContainer(name: "Model")
        
        if inMemory {
            container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
        }
        
        container.loadPersistentStores { storeDescription, error in
            if let _ = error {
                fatalError("Fatal error loading store")
            }
        }
    }
    
    static var preview: DataController = {
        let dataController = DataController(inMemory: true)
        let viewContext = dataController.container.viewContext
        
        do {
            try dataController.createSampleData()
        } catch {
            fatalError("Fatal error creating preview")
        }
        
        return dataController
    }()
    
    func createSampleData() throws {
        let viewContext = container.viewContext
        
        for _ in 1...10 {
            let activity = Activity(context: viewContext)
            activity.name = "run"
        }
        
        try viewContext.save()
    }
}

在应用程序文件中,我进行了以下设置:

struct TestApp: App {
    @StateObject var dataController: DataController
    @Environment(\.managedObjectContext) var managedObjectContext
    
    init() {
        let dataController = DataController()
        _dataController = StateObject(wrappedValue: dataController)
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, dataController.container.viewContext)
        }
    }
}

在我的 ContentView 中,我显示了来自 Core Data 的这个字符串的列表,它工作正常:

struct ContentView: View {
    let activities: FetchRequest<Activity>
    
    init() {
        activities = FetchRequest<Activity>(entity: Activity.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Activity.name, ascending: false)], predicate: nil)
    }
    
    var body: some View {
        List {
            ForEach(activities.wrappedValue) { activity in
                ActivityView(activity: activity)
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var dataController = DataController.preview
    
    static var previews: some View {
        ContentView()
            .environment(\.managedObjectContext, dataController.container.viewContext)
            .environmentObject(dataController)
    }
}

但是在我的 ActivityView 中,我在一个简单的文本字段中显示字符串,预览不起作用。

struct ActivityView: View {
    let activity: Activity
    
    init(activity: Activity) {
        self.activity = activity
    }
    
    var body: some View {
        Text(activity._name)
    }
}

struct ActivityView_Previews: PreviewProvider {
    static var previews: some View {
        ActivityView(activity: Activity.example)
    }
}

我可以在我的列表中看到字符串“运行”10 次,这是设置的方式,但是在 ActivityView 屏幕中我没有看到预览中显示的任何内容。
不知道为什么会这样,我希望有人知道。

编辑:

我在预览中也试过了,但还是不行。

struct ActivityView_Previews: PreviewProvider {
    static var dataController = DataController.preview
    
    static var previews: some View {
        ActivityView(activity: Activity(context: dataController.container.viewContext))
            .environment(\.managedObjectContext, dataController.container.viewContext)
    }
}

在 SwiftUI 中,我们使用视图层次结构将丰富的模型类型转换为简单的类型。因此,解决此问题的最佳方法是重新设计 ActivityView 以使用简单类型而不是模型类型,这样就可以在不创建托管对象的情况下进行预览。我建议观看 Structure your app for SwiftUI previews,其中介绍了这项技术并提供了一些其他技术,例如协议和泛型。

顺便说一句,我也注意到了这个问题:

init() {
    let dataController = DataController()
    _dataController = StateObject(wrappedValue: dataController)
}

StateObject 初始化使用 @autoclosure,例如

@inlinable public init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)

这意味着对象init需要在括号内,例如

_dataController = StateObject(wrappedValue: DataController())

这就是防止对象在每次 SwiftUI 重新计算 View 层次结构时一遍又一遍地初始化的原因。