本文介绍了使用RLMResults.Observe()的多个节更新UITableView的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to create an auto-updating TableView, which is usually easy to do with help of Results.observe (the replacement of .addNotificationBlock)

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

The problem I'm facing is that I can't figure out how to handle a tableView with multiple sections, and cells that can move from 1 section to another.

以下表为例:(如使用Realm和Swift的具有多个节的UITableView )

查理

最大

贝拉

好友

莫莉

Bailey

雏菊

使用

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,但是莫莉"遇到了身份危机,原来是金毛寻回犬,所以我们从详细信息屏幕内更改种族.

Lets' say I have the tableView above, but 'Molly' had an identity crisis and turns out to be a Golden Retriever, so we change the race from within a detail screen.

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

How would I go about handling this change in an Observe block?

我尝试使用1个resultsList/令牌,当我们更改种族属性时会触发修改.但是除了完整的reloadData()(由于需要动画,我无法使用它)之外,我无法弄清楚如何处理删除和删除操作.插入2个不同的部分,因为我们无法在"dog"对象中获得先前的数据.因此,我不知道如何确定单元格是否应该移至其他部分以及上一节是什么.

I have tried using 1 resultsList / token which triggers a modification when we change the race-property. But apart from a full reloadData(), which I can't use because I need animations, I can't figure out how to handle a delete & insert in 2 different sections because we can't reach the previous data in the 'dog'-object. Therefore I don't know how to figure out if a cell should move to a different section and what the previous section was.

我还尝试了按节使用resultsList,但这会导致不一致.当我更改race属性时,它会触发修改(狗对象已更改),删除(上一节的resultList.count为-1​​)和插入(新节的resultList.count = +1) ).这些通知不会在导致错误的完全相同的时间触发:

I also tried using a resultsList per section, but this causes inconsistencies. When I change the race property it triggers a modification (the dog object was changed), a deletion (the resultList.count for the previous section is -1) and an insert (the resultList.count for the new section = +1). These notifications don't trigger at the exact same time which causes the error:

有人知道如何优雅地处理此问题吗?在我正在实习的项目中,实际上我在多个tableView中需要与此类似的东西.

Has anyone figured out how to handle this gracefully? I actually need something similar to this in multiple tableView in the project i'm working on for an internship.

预先感谢

(第一篇文章,所以如果这篇文章不符合标准,请不要犹豫,纠正我)

(First post, so please don't hesitate to correct me when this post is not up to standards)

------使用更多特定的示例代码进行编辑-----

------ EDIT WITH MORE SPECIFIC EXAMPLE CODE -----

我正在使用的数据类已删除了一些不重要的属性

    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()中的代码存根,我想使用它来更新我的tableView并包含2个部分

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

推荐答案

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

This problem bothered me for a long time but I finally figured it out. I hope this helps someone.

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

When an object moves from one result set to the other you should expect two realm notifications. One for the old result set (a deletion) and one for the new result set (an insertion).

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

When we receive a notification for the first result set, then update the tableView for that section, the tableView will call numberOfRowsInSection for BOTH sections. When the tableView realizes that the result set has changed in the other section, but we didn't update the tableView for that section, it complains with NSInternalInconsistencyException.

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

What we need to do is trick the tableView into thinking that the other section was not updated. We do that by maintain a count of our own.

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

Basically, you need to do a few things.

  1. 为每个部分维护单独的结果集
  2. 为每个部分维护单独的通知处理程序
  3. 维护每个部分的手动对象计数
  4. 在通知处理程序因删除或插入而触发时更新对象计数
  5. 返回numberOfRowsInSection中的手动对象计数(没有结果集计数)
  1. Maintain separate result sets for each section
  2. Maintain separate notification handlers for each section
  3. Maintain a manual count of objects for each section
  4. Update the object count when the notification handler fires with deletions or insertions
  5. Return the manual object count in numberOfRowsInSection (NOT THE RESULT SET COUNT)

这是我的模型对象:

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抱怨.

In the sample code I have here there are two sections. The first contains a list of contacts under the age of 70, and the other a list of contacts over the age of 70. By maintaining a count of objects that we update manually when realm notifications fire, we are able to move objects from one result set to the next without UIKit complaining.

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 .

这篇关于使用RLMResults.Observe()的多个节更新UITableView的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

10-21 10:31