我正在尝试创建一个自动更新的 TableView,这通常很容易在Results.observe(替换 .addNotificationBlock)的帮助下完成
我面临的问题是我无法弄清楚如何处理具有多个部分的 tableView,以及可以从一个部分移动到另一个部分的单元格。
以下表为例:(如UITableView with Multiple Sections using Realm 和 Swift 所示)
查理
最大限度
贝拉
伙伴
莫莉
贝利
雏菊
和
class Dog: Object {
@objc dynamic var name String?
@objc dynamic var race: String?
}
Run Code Online (Sandbox Code Playgroud)
然后是:
let results = realm.objects(Dog.self)
let token = dogs.observe { changes in
switch changes {
case .initial(let dogs):
break
case .update:
// HANDLE MOVING CELL TO DIFFERENT SECTION HERE
break
case .error:
break
}
}
Run Code Online (Sandbox Code Playgroud)
假设我有上面的 tableView,但 'Molly' 有身份危机,结果证明是金毛猎犬,所以我们从详细信息屏幕中更改种族。
我将如何处理 Observe 块中的此更改?
我尝试使用 1 个 resultsList/token,当我们更改种族属性时会触发修改。但是除了一个完整的 reloadData(),我因为需要动画而无法使用它,我无法弄清楚如何在 2 个不同的部分中处理删除和插入,因为我们无法访问“狗”中的先前数据'-目的。因此我不知道如何确定一个单元格是否应该移动到不同的部分以及上一部分是什么。
我还尝试使用每个部分的 resultsList,但这会导致不一致。当我更改race属性时,它会触发修改(狗对象已更改)、删除(上一部分的 resultList.count 为 -1)和插入(新部分的 resultList.count = +1)。这些通知不会同时触发,从而导致错误:
'试图从第 x 部分删除项目 x,但更新前只有 x 部分'
有没有人想出如何优雅地处理这个问题?我实际上需要在我正在实习的项目中的多个 tableView 中使用类似的东西。
提前致谢
(第一篇文章,所以当这篇文章不符合标准时,请不要犹豫纠正我)
------ 使用更具体的示例代码进行编辑 -----
我正在使用的数据类删除了一些不重要的属性
class CountInfo: Object, Encodable {
@objc dynamic var uuid: String?
@objc dynamic var productName: String?
// TableView is split in 2 sections based on this boolean-value
@objc dynamic var inStock: Bool = false
}
Run Code Online (Sandbox Code Playgroud)
viewDidLoad() 中的代码存根我想用 2 个部分更新我的 tableView
self.countListProducts = realm.objects(CountInfo.self)
self.token = self.countListProducts.observe {
changes in
AppDelegate.log.debug(changes)
if let tableView = self.tableView {
switch changes {
case .initial:
// if countInfo.isCounted = true: insert in section 0, if false: insert in section 1
// Currently handled by cellForRowAt
tableView.reloadData()
case .update(_, let deletions, let insertions, let modifications):
// Remove deletion rows from correct section
// Insert insertions into correct section
// Reload Cell if modification didn't change 'isCounted' property
// Remove from old section and insert in new section if 'isCounted' property changed
tableView.beginUpdates()
tableView.insertRows(at: insertions.map({ /* GET ROW TO INSERT */ }),
with: .automatic)
tableView.deleteRows(at: deletions.map({ /* GET ROW TO DELETE */ }),
with: .automatic)
tableView.reloadRows(at: modifications.map({ /* UPDATE NAME OR MOVE TO OTHER SECTION IF 'inStock' value Changed */ }),
with: .automatic)
tableView.endUpdates()
case .error(let error):
// An error occurred while opening the Realm file on the background worker thread
fatalError("\(error)")
}
}
Run Code Online (Sandbox Code Playgroud)
这个问题困扰了我很长时间,但我终于解决了。我希望这可以帮助别人。
当对象从一个结果集移动到另一个结果集时,您应该收到两个领域通知。一种用于旧结果集(删除),另一种用于新结果集(插入)。
当我们收到第一个结果集的通知,然后更新该部分的 tableView 时,tableView 将调用numberOfRowsInSection两个部分。当 tableView 意识到其他部分的结果集已更改,但我们没有更新该部分的 tableView 时,它会抱怨NSInternalInconsistencyException.
我们需要做的是欺骗 tableView 认为其他部分没有更新。我们通过维护自己的计数来做到这一点。
基本上,您需要做一些事情。
numberOfRowsInSection(不是结果集计数)这是我的模型对象:
class Contact: Object {
@objc dynamic var uuid: String = UUID().uuidString
@objc dynamic var firstName: String = ""
@objc dynamic var lastName: String = ""
@objc dynamic var age: Int = 0
convenience init(firstName: String, lastName: String, age: Int) {
self.init()
self.firstName = firstName
self.lastName = lastName
self.age = age
}
override class func primaryKey() -> String? {
return "uuid"
}
}
Run Code Online (Sandbox Code Playgroud)
在我这里的示例代码中有两个部分。第一个包含 70 岁以下的联系人列表,另一个包含 70 岁以上的联系人列表。通过维护在领域通知触发时手动更新的对象计数,我们能够从一个结果中移动对象设置为下一个,UIKit 不会抱怨。
let elderAge = 70
lazy var allContacts: Results<Contact> = {
let realm = try! Realm()
return realm.objects(Contact.self)
}()
// KEEP A RESULT SET FOR SECTION 0
lazy var youngContacts: Results<Contact> = {
return allContacts
.filter("age <= %@", elderAge)
.sorted(byKeyPath: "age", ascending: true)
}()
// KEEP A RESULT SET FOR SECTION 1
lazy var elderContacts: Results<Contact> = {
return allContacts
.filter("age > %@", elderAge)
.sorted(byKeyPath: "age", ascending: true)
}()
// MANUALLY MAINTAIN A COUNT OF ALL OBJECTS IN SECTION 0
lazy var youngContactsCount: Int = {
return youngContacts.count
}()
// MANUALLY MAINTAIN A COUNT OF ALL OBJECTS IN SECTION 1
lazy var elderContactsCount: Int = {
return elderContacts.count
}()
// OBSERVE OBJECTS IN SECTION 0
lazy var youngToken: NotificationToken = {
return youngContacts.observe { [weak self] change in
guard let self = self else { return }
switch change {
case .update(_, let del, let ins, let mod):
// MANUALLY UPDATE THE OBJECT COUNT FOR SECTION 0
self.youngContactsCount -= del.count
self.youngContactsCount += ins.count
// REFRESH THE SECTION
self.refresh(section: 0, del: del, ins: ins, mod: mod)
default:
break
}
}
}()
// OBSERVE OBJECTS IN SECTION 1
lazy var elderToken: NotificationToken = {
return elderContacts.observe { [weak self] change in
guard let self = self else { return }
switch change {
case .update(_, let del, let ins, let mod):
// MANUALLY UPDATE THE OBJECT COUNT FOR SECTION 1
self.elderContactsCount -= del.count
self.elderContactsCount += ins.count
// REFRESH THE SECTION
self.refresh(section: 1, del: del, ins: ins, mod: mod)
default:
break
}
}
}()
func refresh(section: Int, del: [Int], ins: [Int], mod: [Int]) {
tableView.beginUpdates()
tableView.deleteRows(
at: del.map { .init(row: $0, section: section) },
with: .automatic
)
tableView.insertRows(
at: ins.map { .init(row: $0, section: section) },
with: .automatic
)
tableView.reloadRows(
at: mod.map { .init(row: $0, section: section) },
with: .automatic
)
tableView.endUpdates()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// RETURN THE MANUALLY CALCULATED OBJECT COUNT (NOT THE ACTUAL RESULT SET COUNT)
//return section == 0 ? youngContacts.count : elderContacts.count
return section == 0 ? youngContactsCount : elderContactsCount
}
Run Code Online (Sandbox Code Playgroud)