【发布时间】:2021-04-29 15:49:49
【问题描述】:
我是 SwiftUI 和 Combine 的新手,并且有一个用于测试的简单项目。我正在使用this article 中概述的CDPublisher 类来创建Core Data 和Combine 之间的桥梁。我已经声明了一个名为ItemEntity 的核心数据实体类。它有一个属性,String,包含一个序列化的 JSON 对象。该对象反序列化为一个名为Item 的结构。我的 SwiftUI 视图只显示从我的 ViewModel 返回的项目列表:
struct ContentView: View {
@ObservedObject
var viewModel: MyListViewModel
var body: some View {
NavigationView {
List {
ForEach(viewModel.items, id: \.self.id) { item in
Text("\(item.name) - \(item.createdDate)")
}
}
.onAppear {
self.viewModel.fetchItems()
}
.toolbar {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
}
private func addItem() {
let entity = ItemEntity(context: viewContext)
let rand = arc4random()
var model = Item(name: "Item \(rand)", createdDate: Date())
model.id = UUID().uuidString
let jsonData = try! JSONEncoder().encode(model)
entity.modelJSON = String(data: jsonData, encoding: .utf8)
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("\(nsError) - \(nsError.userInfo)")
}
}
}
我的ViewModel 使用CDPublisher 类从Core Data 中获取实体。它还有一个 map 函数,可以将每个条目中的 JSON 字符串反序列化为 Item 实例。最终,Item 对象中的Array 是通过我的ViewModel 提供的。它看起来像这样:
import Foundation
import CoreData
import Combine
import SwiftUI
class MyListViewModel: ObservableObject {
private var viewContext: NSManagedObjectContext = PersistenceController.shared.container.viewContext
@Published
var items: [Item] = []
private var cancellables = [AnyCancellable]()
func fetchItems() {
print("FETCHING...")
let decoder = JSONDecoder()
let fetchReq: NSFetchRequest<ItemEntity> = ItemEntity.fetchRequest()
CoreDataPublisher(request: fetchReq, context: self.viewContext)
.map({ (entities: [ItemEntity]) -> [Item] in
var items: [Item] = []
entities.forEach { (entity: ItemEntity) in
if let json = entity.modelJSON?.data(using: .utf8) {
if let item = try? decoder.decode(Item.self, from: json) {
items.append(item)
}
}
}
return items
})
.receive(on: DispatchQueue.main)
.replaceError(with: [])
.eraseToAnyPublisher()
.sink { completion in
print("COMPLETION : \(completion)")
} receiveValue: { items in
print("SUCCESS")
items.forEach { (item) in
print("\t\(item)")
}
self.items = items
}.store(in: &cancellables)
}
}
我的代码编译并运行成功。我看到打印语句源自我的 fetchItems() 方法,表明我的 items 数组包含所有预期的对象:
FETCHING...
SUCCESS
Item(name: "Item 854277542", createdDate: 2021-01-25 19:19:16 +0000)
Item(name: "Item 92334228", createdDate: 2021-01-25 19:19:17 +0000)
Item(name: "Item 405319813", createdDate: 2021-01-25 19:19:18 +0000)
Item(name: "Item 121330574", createdDate: 2021-01-25 19:19:18 +0000)
Item(name: "Item 3025980536", createdDate: 2021-01-25 19:19:19 +0000)
Item(name: "Item 1278077958", createdDate: 2021-01-25 19:19:19 +0000)
Item(name: "Item 4274618146", createdDate: 2021-01-25 19:19:19 +0000)
Item(name: "Item 2320455869", createdDate: 2021-01-25 19:19:19 +0000)
Item(name: "Item 3542559526", createdDate: 2021-01-25 19:19:22 +0000)
Item(name: "Item 4217121551", createdDate: 2021-01-25 19:19:23 +0000)
Item(name: "Item 4139555338", createdDate: 2021-01-25 19:19:24 +0000)
Item(name: "Item 1345067436", createdDate: 2021-01-25 19:20:49 +0000)
但是,我的 UI 没有按预期显示项目。我希望每个 Item 对象都有一行。相反,我看到多行,但它们都具有与我的数组中的 first Item 匹配的完全相同的文本。它本质上是一遍又一遍地重复的同一行。
我显然在这里做错了什么,但我没有足够的经验来确切地知道是什么。为什么我的 items 属性具有正确的值,但我没有在我的 UI 中看到它?是因为我的地图功能和 JSON 解码吗? (另外,有没有更好的方法来完成将ItemEntity 实体数组映射到Item 对象数组?)有什么提示吗?
更新:下面是我的 Item 结构的定义
import Foundation
struct Item {
var name: String = ""
var createdDate: Date = Date()
}
extension Item: Codable {
}
extension Item: Identifiable {
private struct IdentifiableHolder {
static var _id: String = ""
}
var id: String {
get {
return IdentifiableHolder._id
}
set(newId) {
IdentifiableHolder._id = newId
}
}
}
【问题讨论】:
-
请出示物品的声明。我的猜测是您对 Item 的定义有问题 - 只是猜测,因为它没有显示在您的问题中。您拥有显示的代码无法确保您解码的 Item 对象具有唯一的
id值 — 但 List 取决于这些。 -
@matt 添加了 Item 对象。
-
是的,好吧,我仍然看不到您解码的 Item 对象(在组合管道中)如何以
""以外的任何东西作为它们的id。这可以解释问题,不是吗?就我个人而言,我倾向于给 Item 一个初始化器,这样通过提供名称和日期来制作 Item 也将自动应用唯一的id。
标签: swift core-data swiftui combine