使用来自 RLMResults.Observe() 的多个部分更新 UITableView

Jer*_*ter 5 realm swift

我正在尝试创建一个自动更新的 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)

Rob*_*Rob 3

这个问题困扰了我很长时间,但我终于解决了。我希望这可以帮助别人。

当对象从一个结果集移动到另一个结果集时,您应该收到两个领域通知。一种用于旧结果集(删除),另一种用于新结果集(插入)。

当我们收到第一个结果集的通知,然后更新该部分的 tableView 时,tableView 将调用numberOfRowsInSection两个部分。当 tableView 意识到其他部分的结果集已更改,但我们没有更新该部分的 tableView 时,它会抱怨NSInternalInconsistencyException.

我们需要做的是欺骗 tableView 认为其他部分没有更新。我们通过维护自己的计数来做到这一点。

基本上,您需要做一些事情。

  1. 为每个部分维护单独的结果集
  2. 为每个部分维护单独的通知处理程序
  3. 维护每个部分的对象的手动计数
  4. 当通知处理程序因删除或插入而触发时更新对象计数
  5. 返回手动对象计数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)