我是SwiftUI和Combine的新手,并且有一个我用来测试的简单项目。我使用本文中概述的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
类从核心数据中获取实体。它还有一个映射函数,将每个条目中的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()
方法生成的print语句,指示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
对象都有一行。相反,我看到多行,但他们都有完全相同的文本匹配第一个Item
在我的数组。它本质上是一遍又一遍地重复的同一行。
我显然在这里做错了什么,但我没有足够的经验来确切地知道是什么。为什么我的项目属性有正确的值,但我没有看到我的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
}
}
}
您为fetchItems
显示的代码没有确保您解码的Item对象具有唯一的id
值-但是List显式地依赖于这些(id: .self.id
)。相反,所有的Item对象都有默认的id
,即""
。因此,每一个Item对象的id
都是第一个Item对象的id
,解释了这一现象。
你可能在找这样的东西:
struct Item : Codable {
var name: String = ""
var createdDate: Date = Date()
private var idHolder = IdentifiableHolder(id: UUID().uuidString)
}
extension Item: Identifiable {
private struct IdentifiableHolder : Codable {
var id: String = ""
}
var id: String {
get {
return idHolder.id
}
set(newId) {
idHolder.id = newId
}
}
}
现在每个Item都有一个唯一的ID,并且作为其可编码实现的一部分进行存储和检索。