Converting a RealmCollectionChange to an IndexPath in RealmSwift

52 views Asked by At

I have a UITableView that updates when there is a RealmCollectionChange. I want to know how to convert a modified object's child to an IndexPath so that I can reload the relevant row in that TableView section.

Each table section header in my TableView is represented by a Mum in the Granny.secondGeneration List. Each TableViewCell in that section is represented by each Kid object in the Mum.thirdGeneration List.

enter image description here

When a Kid object is modified, I want to access that tableRow Index to reload it. But the modifications array only returns the section/section index, I'm not sure how to get the rowIndex from that to reload just the Kid TableViewCell.

    class Granny:Object {
        
        @Persisted var name:String = ""
        @Persisted var secondGeneration = RealmSwift.List<Mum>()
    }
    
    class Mum:Object {
        
        @Persisted var name:String = ""
        @Persisted var thirdGeneration = RealmSwift.List<Kid>()
    }
    
    class Kid:Object {
        @Persisted var name:String = ""
    }

    ...
  
    let granny = Granny(name: "Sheila")
    let mum = Mum(name: "Mary")
    granny.secondGeneration.append(mum)
    let kid1 = Kid(name: "Lola")
    let kid2 = Kid(name: "Greg")
    mum.thirdGeneration.append(kid1)
    mum.thirdGeneration.append(kid2)
    
    RealmManager.add(object: granny)
    ...
    notificationToken = granny.secondGeneration.observe { [weak self] (changes: RealmCollectionChange) in
        guard let tableView = self?.tableView else { return }
        switch changes {
        case .initial:
            // Results are now populated and can be accessed without blocking the UI
            tableView.reloadData()
        case .update(_, let deletions, let insertions, let modifications):
            
            print("Insertions: \(insertions.count) Deletions:\(deletions.count) Modifications: \(modifications)")

tableView.beginUpdates()
                    
                    
                    if !modifications.isEmpty {
                        let modificationsSectionIndex = modifications.map({ IndexSet(integer: $0) })[0]
                        // This reloads the 'Mum' section header, but I want to find out the row index of the modified child 
                        tableView.reloadSections(modificationsSectionIndex, with: .automatic)
                    }

                    tableView.endUpdates()
            
        case .error(let error):
            // An error occurred while opening the Realm file on the background worker thread
            fatalError("\(error)")
        }
    }
1

There are 1 answers

0
Jay On BEST ANSWER

Allow me to approach this at a high level with simplified code - I think that will make the solution more clear.

Answer: Change your logic. Instead of observing the Parent class looking for changes in Children, observe the Children class who know who their Parent is.

Here's the setup using Persons and Dogs, which is analogous to a parent->child relationship

class PersonClass: Object {
    @Persisted var name = ""
    @Persisted var dogList = RealmSwift.List<DogClass>()
}

class DogClass: Object {
    @Persisted var name = ""
    @Persisted(originProperty: "dogList") var linkedPersons: LinkingObjects<PersonClass>
}

There are persons who have dogs (similar to your setup) so Realm will look like this

person0
  dog0
  dog1
person2
  dog3
  dog4

Then, populate dogResults and attach an observer

self.dogResults = realm.objects(DogClass.self)
self.dogsToken = self.dogResults!.observe { changes in

And then here's what happens in the update (note addition for first parameter 'changedItems'

case .update(let changedItems, let deletions, let insertions, let modifications ):

section: modified

let paths = modifications.map { (index) -> IndexPath in
    //the dog that was modified in the dogs results
    let modifiedDog = changedItems[index]

    //the owner of this dog e.g. the section header
    let owner = modifiedDog.linkingPerson.first! 

    //the index of the person section
    let sectionIndex = self.peopleResults?.index(of: owner)

    //the row within the section of this modified dog
    let rowIndex = owner.dogList.index(of: modifiedDog) 

    //the IndexPath to the dog row within the section
    let path = IndexPath(item: rowIndex!, section: sectionIndex!) 

    print(path) //print each path

    return path
}

We now have and array of index paths for the sections/rows that need to be updated. If it's never going to be multiple row updates at the same time, the map can be removed to just address one row at a time