How to use FetchedResultsControllerDelegate with macOS

Can someone please point me towards documentation and/or sample code on using the FetchedResultsControllerDelegate with macIS (Swift)?

I see a lot of information on how to use it with iOS. The examples for iOS include:

func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {

switch type {

case .insert:

tableView.insertRows(at: [newIndexPath!], with: .automatic)

case .delete:

tableView.deleteRows(at: [indexPath!], with: .automatic)

case .update:

configureCell(tableView.cellForRow(at: indexPath!)!, withEvent: anObject as! Event)

case .move:

configureCell(tableView.cellForRow(at: indexPath!)!, withEvent: anObject as! Event)

tableView.moveRow(at: indexPath!, to: newIndexPath!)

}

}

With macOS, there are no deleteRows functions. There is a removeRows but that uses an IndexSet and not IndexPath.

Accepted Reply

Hello nigelfromtewkesbury, you were right my previous anwer was wrong, I setup a sample project as follows:


import Cocoa
import CoreData
class ViewController: NSViewController {
   
    static let nameCellIDIdentifier = "NameCellIDIdentifier"
    @objc lazy var managedObjectContext: NSManagedObjectContext = {
        let appDelegate = NSApplication.shared.delegate as! AppDelegate
        return appDelegate.persistentContainer.viewContext
    }()
    var _fetchedResultsController: NSFetchedResultsController<Event>?

    var fetchedResultsController: NSFetchedResultsController<Event> {
        if _fetchedResultsController != nil {
            return _fetchedResultsController!
        }
       
        let fetchRequest: NSFetchRequest<Event> = Event.fetchRequest()
        fetchRequest.predicate = NSPredicate(value: true)
        fetchRequest.fetchBatchSize = 20
        let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
        fetchRequest.sortDescriptors = [sortDescriptor]
        let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
        aFetchedResultsController.delegate = self
        _fetchedResultsController = aFetchedResultsController
       
        do {
            try _fetchedResultsController!.performFetch()
        } catch {
            let nserror = error as NSError
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
        }
       
        return _fetchedResultsController!
    }

    @IBOutlet weak var tableView: NSTableView!
   
    @IBAction func addEvent(_ sender: Any) {
        let date = Date()
        let event = Event(context: managedObjectContext)
        event.name = "Event for \(date)"

        do {
            try managedObjectContext.save()
        } catch {
            let nserror = error as NSError
            NSApplication.shared.presentError(nserror)
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self
    }

    fileprivate func configureCell(_ cell: NSTableCellView, withEvent event: Event){
        if let name = event.name{
           cell.textField?.stringValue = name
        }
    }
}
extension ViewController : NSTableViewDataSource{
    func numberOfRows(in tableView: NSTableView) -> Int{
        return fetchedResultsController.fetchedObjects?.count ?? 0
    }
}
extension ViewController : NSTableViewDelegate{
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        guard let event = fetchedResultsController.fetchedObjects?[row] else { fatalError("Can not get Event") }
        if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: ViewController.nameCellIDIdentifier), owner: nil) as? NSTableCellView {
             cell.textField?.stringValue = event.name ?? ""
            return cell
        }
        return nil
    }
}
extension ViewController : NSFetchedResultsControllerDelegate{
   
    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?){
        switch type {
        case .insert:
            if let indexPath = newIndexPath {
                tableView.insertRows(at: IndexSet(integer:indexPath.item), withAnimation: .effectFade)
            }
        case .delete:
            if let indexPath = indexPath {
                tableView.removeRows(at: IndexSet(integer:indexPath.item), withAnimation: .effectFade)
            }
        case .move:
            if let indexPath = indexPath {
                tableView.removeRows(at: IndexSet(integer:indexPath.item), withAnimation: .effectFade)
            }
           
            if let newIndexPath = newIndexPath {
                tableView.insertRows(at: IndexSet(integer:newIndexPath.item), withAnimation: .effectFade)
            }
        case .update:
            if let indexPath = indexPath, let cell = tableView.view(atColumn: 0, row: indexPath.item, makeIfNecessary: true) as? NSTableCellView {
                configureCell(cell, withEvent: anObject as! Event)
            }
        }
    }
}

Replies

      tableView.removeRows(at: IndexSet(integer:indexPath!.row), withAnimation: NSTableView.AnimationOptions.effectFade)

Thanks for the reply. I had tried that, but it doesn't compile. ( Value of type 'IndexPath' has no member 'row' )

Hello nigelfromtewkesbury, you were right my previous anwer was wrong, I setup a sample project as follows:


import Cocoa
import CoreData
class ViewController: NSViewController {
   
    static let nameCellIDIdentifier = "NameCellIDIdentifier"
    @objc lazy var managedObjectContext: NSManagedObjectContext = {
        let appDelegate = NSApplication.shared.delegate as! AppDelegate
        return appDelegate.persistentContainer.viewContext
    }()
    var _fetchedResultsController: NSFetchedResultsController<Event>?

    var fetchedResultsController: NSFetchedResultsController<Event> {
        if _fetchedResultsController != nil {
            return _fetchedResultsController!
        }
       
        let fetchRequest: NSFetchRequest<Event> = Event.fetchRequest()
        fetchRequest.predicate = NSPredicate(value: true)
        fetchRequest.fetchBatchSize = 20
        let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
        fetchRequest.sortDescriptors = [sortDescriptor]
        let aFetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: managedObjectContext, sectionNameKeyPath: nil, cacheName: nil)
        aFetchedResultsController.delegate = self
        _fetchedResultsController = aFetchedResultsController
       
        do {
            try _fetchedResultsController!.performFetch()
        } catch {
            let nserror = error as NSError
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
        }
       
        return _fetchedResultsController!
    }

    @IBOutlet weak var tableView: NSTableView!
   
    @IBAction func addEvent(_ sender: Any) {
        let date = Date()
        let event = Event(context: managedObjectContext)
        event.name = "Event for \(date)"

        do {
            try managedObjectContext.save()
        } catch {
            let nserror = error as NSError
            NSApplication.shared.presentError(nserror)
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self
    }

    fileprivate func configureCell(_ cell: NSTableCellView, withEvent event: Event){
        if let name = event.name{
           cell.textField?.stringValue = name
        }
    }
}
extension ViewController : NSTableViewDataSource{
    func numberOfRows(in tableView: NSTableView) -> Int{
        return fetchedResultsController.fetchedObjects?.count ?? 0
    }
}
extension ViewController : NSTableViewDelegate{
    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        guard let event = fetchedResultsController.fetchedObjects?[row] else { fatalError("Can not get Event") }
        if let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: ViewController.nameCellIDIdentifier), owner: nil) as? NSTableCellView {
             cell.textField?.stringValue = event.name ?? ""
            return cell
        }
        return nil
    }
}
extension ViewController : NSFetchedResultsControllerDelegate{
   
    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?){
        switch type {
        case .insert:
            if let indexPath = newIndexPath {
                tableView.insertRows(at: IndexSet(integer:indexPath.item), withAnimation: .effectFade)
            }
        case .delete:
            if let indexPath = indexPath {
                tableView.removeRows(at: IndexSet(integer:indexPath.item), withAnimation: .effectFade)
            }
        case .move:
            if let indexPath = indexPath {
                tableView.removeRows(at: IndexSet(integer:indexPath.item), withAnimation: .effectFade)
            }
           
            if let newIndexPath = newIndexPath {
                tableView.insertRows(at: IndexSet(integer:newIndexPath.item), withAnimation: .effectFade)
            }
        case .update:
            if let indexPath = indexPath, let cell = tableView.view(atColumn: 0, row: indexPath.item, makeIfNecessary: true) as? NSTableCellView {
                configureCell(cell, withEvent: anObject as! Event)
            }
        }
    }
}

Sorry - hadn't seen your reply until just now. I won't be able to check this out until tomorrow. So it looks like instead of indexPath.row you use indexPath.item??

Thanks for your help.

Tried this out today - works well 🙂 Thanks very much for your help.

Actually, although this seemed to work e.g. I could add items and delete them from the table, I notice a problem when looking in the debug console.

When I delete a table entry, I see:

[0, 1]

2018-02-28 14:05:37.900499+0000 CardLists[24219:2320802] [error] error: NSFetchedResultsController: no object at index 18446744073709551615 in section at index 0

Note, the [0, 1] is what I get with a debug message just before the removeRows ( print (indexPath.debugDescription))

Hello nigelfromtewkesbury,


I managed to delete a table entry without this error as follow:


extension ViewController : NSTableViewDelegate{
    func tableViewSelectionDidChange(_ notification: Notification){
        print("notification: \(notification)")
        let table = notification.object as! NSTableView
        let selectedRowIndexes = table.selectedRowIndexes.map { Int($0) }
        guard let index = selectedRowIndexes.first else { return }
        let indexPath = IndexPath(item: index, section: 0)
      
        let event = fetchedResultsController.object(at: indexPath)
        managedObjectContext.delete(event)
        do {
            try managedObjectContext.save()
        } catch {
            let nserror = error as NSError
            NSApplication.shared.presentError(nserror)
            fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
        }
    }
}