使用 CoreDataViewModel 时如何在 Canvas 中预览 Core Data 对象
Posted
技术标签:
【中文标题】使用 CoreDataViewModel 时如何在 Canvas 中预览 Core Data 对象【英文标题】:How to preview Core Data objects in Canvas when using a CoreDataViewModel 【发布时间】:2021-09-17 11:13:09 【问题描述】:以下代码有效地使用@StateObject
和@ObservedObject
包装器来捕获来自ObservableObject
视图模型的更改。一切都按预期工作,除了我希望能够在 Canvas 中显示虚拟数据以用于设计目的。正如您从下面的代码中看到的那样,我能够创建一个Car
对象inMemory
并在Preview
结构中有效地检索它,我的问题是在尝试显示多辆汽车时,如您所见,显示多辆汽车 我不得不在Preview
结构中复制List
的代码以用于CarsView
。在下面的代码中,这似乎没什么大不了的,因为我删除了很多构成 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...
第二视图/汽车视图
我的问题是,在这里,我从 Preview 结构中的主视图复制代码。
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()
【问题讨论】:
如果我理解正确,您担心代码重复。重复发生在哪里:在模型中,在视图中? 在 CarsView 的预览中,我有一条评论说//repeated code
。当然,这里我只展示了 List 的最少代码,但是代码太多了,当同时更新 UI 时,View 和 Preview 都感觉不对。
它就在那里,查看第二个视图CarsView
的预览。我留下 CoreData 代码仅供参考,以便更好地了解我是如何创建对象的。
可能是***有问题,我在CarsView的预览中看到重复的代码,是我帖子里的第二个sn-p代码。
问题的原因在于 View 和 CoreData 之间的紧密耦合。为了让您了解:不要将视图模型命名为“CoreDataViewModel”(how) - 而是将其命名为“CarViewModel” - (what!)和 完全隐藏您正在使用托管对象和 CoreData 的事实。然后对于预览(和测试),在 DI 的帮助下,您可以在创建时设置一个“模拟存储库”in CarViewModel。
【参考方案1】:
预览的目的是使用带有一些给定数据的普通视图,而不是从实际视图中复制代码。除此之外,您似乎还有很多东西。
我会先将 Core Data 管理器类注入到视图模型中:
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
正如其中一个 cmets 所述,您应该考虑将您的视图模型重命名为与 CarsViewModel 或 CarListViewModel 等汽车有关的名称。
【讨论】:
成功了,非常感谢您的帮助! @ Joakim Danielson - 我非常感谢你,这对我在 CoreData 中的所有数据对象都非常有效,太棒了!不幸的是,我对依赖注入并不是很熟悉。快速提问,除了帮助 Canvas 中的预览之外,将 Core Data 管理器类注入视图模型还有其他好处吗?在这里,我只是想更好地理解我们对视图模型所做的注入。更好的是,这真的被认为是依赖注入吗? 是的,这是依赖注入,正如您在此实现中看到的那样,您在视图模型和 CoreDataManager 类之间没有硬编码的依赖关系,因此您可以注入不同的类型。这不仅对预览有用,而且对测试有用。为了更进一步,可以使用协议来代替,这样您就可以注入任何符合该协议的类型。 感谢您的澄清和良好的建议。我将仔细研究DI。非常感谢您的所有帮助!以上是关于使用 CoreDataViewModel 时如何在 Canvas 中预览 Core Data 对象的主要内容,如果未能解决你的问题,请参考以下文章
如何在使用 observable 更新数组时更新 *ngFor 以及如何在网站加载时初始化 observable?
在使用 jupyter notebook 时如何在 pandas 中使用 Dataframe 时查看完整数据? [复制]
按下标签栏项目时如何显示警报视图以及在其他目标中使用时如何忽略它?
在 Rails 6 中使用 activestorage 时,如何在重新显示表单时保留文件?