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

时间:2017-11-22 09:44:29

标签: swift realm

我正在尝试创建一个自动更新的TableView,这通常很容易在Results.observe的帮助下完成(替换.addNotificationBlock)

我面临的问题是我无法弄清楚如何处理具有多个部分的tableView,以及可以从1个部分移动到另一个部分的单元格。

以下表为例:(见UITableView with Multiple Sections using Realm and Swift

牛头犬

查理

最高

德国牧羊犬

贝拉

好友

莫利

金毛猎犬

贝利

西伯利亚哈士奇

。通过

class Dog: Object {
@objc dynamic var name String?
@objc dynamic var race: String?
}

然后是:

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
         }
     }

让我们说上面有tableView,但是'Molly'有一个身份危机,结果证明是一个金毛猎犬,所以我们在一个细节屏幕内改变了比赛。

如何在Observe块中处理此更改?

我尝试使用1个resultsList / token,当我们更改race-property时会触发修改。但除了一个完整的reloadData(),我无法使用,因为我需要动画,我无法弄清楚如何处理删除&插入2个不同的部分,因为我们无法访问'dog'对象中的先前数据。因此,我不知道如何确定一个单元格是否应该移动到另一个部分以及上一部分是什么。

我也尝试过每个部分使用一个结果列表,但这会导致不一致。当我更改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 
                }

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)")
                }
            }

1 个答案:

答案 0 :(得分:0)

这个问题困扰了我很长时间,但我终于想通了。我希望这对某人有帮助。

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

当我们收到第一个结果集的通知,然后更新该节的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"
    }
}

在示例代码中,我有两个部分。第一个包含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
}

这是完整源文件的LINK