swiftrealmrealm-list

Converting a RealmCollectionChange to an IndexPath in RealmSwift


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

Solution

  • 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