ListSectionOberserver not called on insert or delete #289

Closed
opened 2025-12-29 18:25:21 +01:00 by adam · 3 comments
Owner

Originally created by @timfraedrich on GitHub (Oct 3, 2019).

CoreStore: 6.3.1
Swift: 5.1
Xcode: 11.0
Simulator: Xr iOS 13.0

I'm migrating all CoreData related code and models in my project to CoreStore right now, because it just seemed more convenient to me. But while replacing an NSFetchedResultsController with a sectioned list Monitor and ListSectionObserver I encountered the issue that the list does not get refreshed after an object is inserted, removed or dealt with in any other manner, because the functions of the Observer do not get called. That ultimately leads to the list being incomplete and wrong objects getting returned when clicking on a cell or - in the case of deletion - to a crash.

Here is the code I wrote:


struct DataManager {

    static let monitor = CoreStore.monitorSectionedList(
        From<MyCoreStoreObject>()
            .sectionBy(\.dayIdentifier)
            .orderBy(.descending(\.startDate))
    )

}

class ViewController: UITableViewController, ListSectionObserver {
    
    typealias ListEntityType = MyCoreStoreObject

    override func viewDidLoad() {
        DataManager.monitor.addObserver(self)
        
        super.viewDidLoad()
    }
    
    deinit {
        DataManager.monitor.removeObserver(self)
    }

    // MARK: TableView
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return DataManager.monitor.numberOfSections()
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        guard let sectionCount = DataManager.monitor.numberOfObjects(safelyIn: section) else {
            return 0
        }
        return sectionCount
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let object = DataManager.monitor.objects(in: indexPath.section)[indexPath.row]
        let cell = ObjectCell(object: object)
        return cell
    }
    
    override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let dayIdentifier = DataManager.monitor.sectionIndexTitles()[section]
        let header = ListHeader(dayIdentifier: dayIdentifier)
        return header
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        let workout = DataManager.monitor.objects(in: indexPath.section)[indexPath.row]
        
        // display view controller
        
    }
    
    // MARK: ListObserver
    
    func listMonitorWillChange(_ monitor: ListMonitor<MyCoreStoreObject>) {
        self.tableView.beginUpdates()
    }
    
    func listMonitorDidChange(_ monitor: ListMonitor<MyCoreStoreObject>) {
        self.tableView.endUpdates()
    }
    
    func listMonitorWillRefetch(_ monitor: ListMonitor<MyCoreStoreObject>) {
        
    }
    
    func listMonitorDidRefetch(_ monitor: ListMonitor<MyCoreStoreObject>) {
        self.tableView.reloadData()
    }
    
    // MARK: ListObjectObserver
    
    func listMonitor(_ monitor: ListMonitor<MyCoreStoreObject>, didInsertObject object: MyCoreStoreObject, toIndexPath indexPath: NSIndexPath) {
        let indexPath = indexPath as IndexPath
        self.tableView.insertRows(at: [indexPath], with: .fade)
    }
    
    func listMonitor(_ monitor: ListMonitor<MyCoreStoreObject>, didDeleteObject object: MyCoreStoreObject, fromIndexPath indexPath: NSIndexPath) {
        let indexPath = indexPath as IndexPath
        self.tableView.deleteRows(at: [indexPath], with: .fade)
    }
    
    func listMonitor(_ monitor: ListMonitor<MyCoreStoreObject>, didUpdateObject object: MyCoreStoreObject, atIndexPath indexPath: NSIndexPath) {
        let indexPath = indexPath as IndexPath
        if let cell = self.tableView.cellForRow(at: indexPath) as? ObjectCell {
            cell.object = object
            cell.setup()
        }
    }
    
    func listMonitor(_ monitor: ListMonitor<MyCoreStoreObject>, didMoveObject object: MyCoreStoreObject, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
        let fromIndexPath = fromIndexPath as IndexPath
        self.tableView.deleteRows(at: [fromIndexPath], with: .fade)
        let toIndexPath = toIndexPath as IndexPath
        self.tableView.insertRows(at: [toIndexPath], with: .fade)
    }
    
    // MARK: ListSectionObserver
    
    func listMonitor(_ monitor: ListMonitor<MyCoreStoreObject>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
        self.tableView.insertSections(IndexSet(arrayLiteral: sectionIndex), with: .fade)
    }
    
    func listMonitor(_ monitor: ListMonitor<MyCoreStoreObject>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
        self.tableView.deleteSections(IndexSet(arrayLiteral: sectionIndex), with: .fade)
    }
    
}

I would greatly appreciate any help, maybe I'm just forgetting something really obvious.

Originally created by @timfraedrich on GitHub (Oct 3, 2019). CoreStore: 6.3.1 Swift: 5.1 Xcode: 11.0 Simulator: Xr iOS 13.0 I'm migrating all CoreData related code and models in my project to CoreStore right now, because it just seemed more convenient to me. But while replacing an NSFetchedResultsController with a sectioned list Monitor and ListSectionObserver I encountered the issue that the list does not get refreshed after an object is inserted, removed or dealt with in any other manner, because the functions of the Observer do not get called. That ultimately leads to the list being incomplete and wrong objects getting returned when clicking on a cell or - in the case of deletion - to a crash. Here is the code I wrote: ```swift struct DataManager { static let monitor = CoreStore.monitorSectionedList( From<MyCoreStoreObject>() .sectionBy(\.dayIdentifier) .orderBy(.descending(\.startDate)) ) } class ViewController: UITableViewController, ListSectionObserver { typealias ListEntityType = MyCoreStoreObject override func viewDidLoad() { DataManager.monitor.addObserver(self) super.viewDidLoad() } deinit { DataManager.monitor.removeObserver(self) } // MARK: TableView override func numberOfSections(in tableView: UITableView) -> Int { return DataManager.monitor.numberOfSections() } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { guard let sectionCount = DataManager.monitor.numberOfObjects(safelyIn: section) else { return 0 } return sectionCount } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let object = DataManager.monitor.objects(in: indexPath.section)[indexPath.row] let cell = ObjectCell(object: object) return cell } override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? { let dayIdentifier = DataManager.monitor.sectionIndexTitles()[section] let header = ListHeader(dayIdentifier: dayIdentifier) return header } override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let workout = DataManager.monitor.objects(in: indexPath.section)[indexPath.row] // display view controller } // MARK: ListObserver func listMonitorWillChange(_ monitor: ListMonitor<MyCoreStoreObject>) { self.tableView.beginUpdates() } func listMonitorDidChange(_ monitor: ListMonitor<MyCoreStoreObject>) { self.tableView.endUpdates() } func listMonitorWillRefetch(_ monitor: ListMonitor<MyCoreStoreObject>) { } func listMonitorDidRefetch(_ monitor: ListMonitor<MyCoreStoreObject>) { self.tableView.reloadData() } // MARK: ListObjectObserver func listMonitor(_ monitor: ListMonitor<MyCoreStoreObject>, didInsertObject object: MyCoreStoreObject, toIndexPath indexPath: NSIndexPath) { let indexPath = indexPath as IndexPath self.tableView.insertRows(at: [indexPath], with: .fade) } func listMonitor(_ monitor: ListMonitor<MyCoreStoreObject>, didDeleteObject object: MyCoreStoreObject, fromIndexPath indexPath: NSIndexPath) { let indexPath = indexPath as IndexPath self.tableView.deleteRows(at: [indexPath], with: .fade) } func listMonitor(_ monitor: ListMonitor<MyCoreStoreObject>, didUpdateObject object: MyCoreStoreObject, atIndexPath indexPath: NSIndexPath) { let indexPath = indexPath as IndexPath if let cell = self.tableView.cellForRow(at: indexPath) as? ObjectCell { cell.object = object cell.setup() } } func listMonitor(_ monitor: ListMonitor<MyCoreStoreObject>, didMoveObject object: MyCoreStoreObject, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) { let fromIndexPath = fromIndexPath as IndexPath self.tableView.deleteRows(at: [fromIndexPath], with: .fade) let toIndexPath = toIndexPath as IndexPath self.tableView.insertRows(at: [toIndexPath], with: .fade) } // MARK: ListSectionObserver func listMonitor(_ monitor: ListMonitor<MyCoreStoreObject>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) { self.tableView.insertSections(IndexSet(arrayLiteral: sectionIndex), with: .fade) } func listMonitor(_ monitor: ListMonitor<MyCoreStoreObject>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) { self.tableView.deleteSections(IndexSet(arrayLiteral: sectionIndex), with: .fade) } } ``` I would greatly appreciate any help, maybe I'm just forgetting something really obvious.
adam closed this issue 2025-12-29 18:25:21 +01:00
Author
Owner

@JohnEstropia commented on GitHub (Oct 3, 2019):

How does your transaction insert those objects? Can you show some code from the transactions side?

@JohnEstropia commented on GitHub (Oct 3, 2019): How does your transaction insert those objects? Can you show some code from the transactions side?
Author
Owner

@timfraedrich commented on GitHub (Oct 3, 2019):

Can you show some code from the transaction

Yes of course:

static func saveObject(..., completion: @escaping (Bool, Error?, MyCoreStoreObject?) -> Void) {
        
    CoreStore.perform(asynchronous: { (transaction) -> MyCoreStoreObject in
        let object = transaction.create(Into<MyCoreStoreObject>())
        object. ...

        for data in dataArray {
            let dataObject = transaction.create(Into<DataObject>())
            dataObject. ...
            dataObject.object .= object
        }

        return object

    }) { (result) in
        switch result {
        case .success(let object):
            completion(true, nil, object)
        case .failure(let error):
            completion(false, error, nil)
        }
    }
}

I did try to insert it in other ways though (with unsafe and synchronous transactions) and that did not do anything different. The object is also there after I restart the app. The Observer is just not notified of a change.

@timfraedrich commented on GitHub (Oct 3, 2019): > Can you show some code from the transaction Yes of course: ```swift static func saveObject(..., completion: @escaping (Bool, Error?, MyCoreStoreObject?) -> Void) { CoreStore.perform(asynchronous: { (transaction) -> MyCoreStoreObject in let object = transaction.create(Into<MyCoreStoreObject>()) object. ... for data in dataArray { let dataObject = transaction.create(Into<DataObject>()) dataObject. ... dataObject.object .= object } return object }) { (result) in switch result { case .success(let object): completion(true, nil, object) case .failure(let error): completion(false, error, nil) } } } ``` I did try to insert it in other ways though (with unsafe and synchronous transactions) and that did not do anything different. The object is also there after I restart the app. The Observer is just not notified of a change.
Author
Owner

@timfraedrich commented on GitHub (Oct 4, 2019):

Okay I found the solution: It turns out one should not blindly copy code from the documentation. It seems that it is not accurate anymore, since the NSIndexPath changed to IndexPath. The Observer now gets called with no issues.

@timfraedrich commented on GitHub (Oct 4, 2019): Okay I found the solution: It turns out one should not blindly copy code from the documentation. It seems that it is not accurate anymore, since the NSIndexPath changed to IndexPath. The Observer now gets called with no issues.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/CoreStore-JohnEstropia#289