使用 CoreDataViewModel 时如何在 Canvas 中预览核心数据对象

How to preview Core Data objects in Canvas when using a CoreDataViewModel

以下代码有效地使用了 @StateObject@ObservedObject 包装器来捕获来自 ObservableObject 视图模型的更改。一切都按预期工作,除了我希望能够在 Canvas 中显示虚拟数据以用于设计目的。正如您从下面的代码中看到的,我能够创建一个 Car 对象 inMemory 并在 Preview 结构中有效地检索它,我的问题是在尝试显示多辆汽车时,如您所见,为了显示多辆汽车,我必须在 CarsViewPreview 结构中复制 List 的代码。在下面的代码中,这似乎没什么大不了的,因为我删除了很多构成 List 的代码,但在我的生产代码中,我有很多代码来自定义行。

这真的是显示通常由 ViewModel 管理的数据的最佳方式吗?

有没有一种无需重复大量 SwiftUI 代码即可显示数据的方法?

SwiftUI 视图

父视图/内容视图

    struct ContentView: View {
        @StateObject var coreDataViewModel = CoreDataViewModel()

        var body: some View {
            
            TabView(selection: 1){
                
                CarsView(coreDataViewModel: coreDataViewModel)
                
                .tabItem {
                    Text("Cars")
                }.tag(1)

                // other tabs...
            }
        }
    }

第二视图/汽车视图

我的问题是,我在预览结构中复制主视图中的代码。

    struct CarsView: View {
        @ObservedObject var coreDataViewModel:CoreDataViewModel
        var body: some View {
            List {
                ForEach(coreDataViewModel.cars) { car in
                    // custom row to display cars
                }
            }
        }
    }

    struct CarsView_Previews: PreviewProvider {
        @Environment(\.managedObjectContext) private var viewContext
        static var previews: some View {

            CarsView(coreDataViewModel: CoreDataViewModel())
            
            
            let context = CoreDataManager.preview.container.viewContext
            let requestCar: NSFetchRequest<Car> = Car.fetchRequest()
            let fetchedCar = (try! context.fetch(requestCar).first)!
            
            let cars = [fetchedCar]
            // repeated code 
            List{
                ForEach(cars) { car in
                    Text(car.make ?? "")
                }
            }
        }
    }

核心数据

实体和属性

汽车

核心数据管理器

    class CoreDataManager{

        static let instance = CoreDataManager()
        
        static var preview: CoreDataManager = {
            let result = CoreDataManager(inMemory: true)
            let viewContext = result.container.viewContext
            // new car
            let car = Car(context: viewContext)
            car.make = "Ford"
            car.model = "Mustang"
            
            do {
                try viewContext.save()
            } catch {
            
            }
            return result
        }()
        
        let container: NSPersistentContainer
        let context: NSManagedObjectContext
        
        init(inMemory: Bool = false){
            container = NSPersistentContainer(name: "CoreDataContainer")
            
            if inMemory {
                container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
            }
            
            container.loadPersistentStores { (description, error) in
                if let error = error{
                    print("Error loading Core Data. \(error)")
                }
            }
            context = container.viewContext
        }
        
        func save(){
            do{
                try context.save()
                print("Saved successfully!")
            }catch let error{
                print("Error saving Core Data. \(error.localizedDescription)")
            }
        }
    }

核心数据视图模型

    class CoreDataViewModel: ObservableObject{
        
        let manager = CoreDataManager.instance
        
        @Published var cars: [Car] = []
        
        init(){
            getCars()
        }
        
        // addCar, deleteCar, updateCar etc. methods...

        func getCars(){
            let request = NSFetchRequest<Car>(entityName: "Car")

            let sort = NSSortDescriptor(keyPath: \Car.model, ascending: true)
            request.sortDescriptors = [sort]

            do{
                cars =  try manager.context.fetch(request)
            }catch let error{
                print("Error fetching businesses. \(error.localizedDescription)")
            }
        }
        
        func save(){
            self.manager.save()
        }
    }

预览的目的是使用普通视图和一些给定的数据,而不是从实际视图中复制代码。除此之外,你似乎还有很多东西。

我会将核心数据管理器 class 注入视图模型以开始:

class CoreDataViewModel: ObservableObject{        
    let manager: CoreDataManager
    
    @Published var cars: [Car] = []
    
    init(coreDataManager: CoreDataManager = .instance){
        self.manager = coreDataManager
        getCars()
    }

那么我会把预览简化为

struct CarsView_Previews: PreviewProvider {
    static var previews: some View {
        CarsView(coreDataViewModel: CoreDataViewModel(coreDataManager: .preview))
    }
}

您已经在 preview 中创建了一个 Car 对象,所以为什么不在循环中执行此操作,这里我已将其移动到一个单独的函数中,可以从 preview 声明中调用或单独

#if DEBUG
extension CoreDataManager {
    func createMockCars() {
        for i in 1...5 {
             let car = Car(context: self.context)
             car.make = "Make \(i)"
             car.model = "Model \(i)"
        }
        try! self.context.save()
    }
}
#endif

如其中一条评论所述,您应该考虑将视图模型重命名为与 CarsViewModel 或 CarListViewModel 等汽车有关的名称。