NSTableView with Core Data Tutorial using NSFetchedResultsController

In this tutorial, I will walk you through using an NSTableView with Core Data in a macOS application. We will use NSFetchedResultsController to manage the data and perform the necessary updates.

This tutorial assumes you have some familiarity with Swift, Xcode and Cocoa (macOS) development using storyboards.

    1. Create a new macOS project in Xcode: Open Xcode and create a new macOS project using the “App” template. Name the project and make sure to check the “Use Core Data” checkbox.
    2. Set up the Core Data model:
      • Open the .xcdatamodeld file in your project.
      • Click on “Add Entity” to create a new entity. Name it “Item” for this example.
      • Add two attributes to the “Item” entity: “title” with a “String” type and “createdAt” with a “Date” type.
    3. Create the User Interface:
      • Open the Main.storyboard file.
      • Drag an NSTableView from the Object Library to your window. Make sure to also include the scroll view.
      • Set up Auto Layout constraints for the NSTableView to fit the window.
      • Add two columns to the NSTableView and set their titles to “Title” and “Created At” respectively.
    4. Connect the NSTableView to the code:
      • Open the ViewController.swift file.
      • Add an @IBOutlet for the NSTableView and connect it to the table view in the storyboard:
        
        @IBOutlet weak var tableView: NSTableView!
        
    5. Set up the NSFetchedResultsController:
      • Import the necessary frameworks at the top of your ViewController.swift file:
        
        import Cocoa
        import CoreData
        
      • Create a lazy property for the NSFetchedResultsController:
        
        lazy var fetchedResultsController: NSFetchedResultsController = {
            let fetchRequest: NSFetchRequest = Item.fetchRequest()
            fetchRequest.sortDescriptors = [NSSortDescriptor(key: "createdAt", ascending: true)]
        
            let frc = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)
            frc.delegate = self
            return frc
        }()
        
        

         

        Make sure to replace persistentContainer with the name of the NSPersistentContainer instance in your AppDelegate if it’s different. You can access it using NSApp.delegate as! AppDelegate.

    6. Conform to NSTableViewDataSource and NSTableViewDelegate:
      • Make your ViewController conform to these protocols:class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate, NSFetchedResultsControllerDelegate {
      • Implement the required NSTableViewDataSource methods:
        
        func numberOfRows(in tableView: NSTableView) -> Int {
            return fetchedResultsController.sections?.first?.numberOfObjects ?? 0
        }
        
        func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
            let item = fetchedResultsController.object(at: IndexPath(item: row, section: 0))
        
            if tableColumn?.identifier == NSUserInterfaceItemIdentifier("TitleColumn") {
                return item.title
            } else if tableColumn?.identifier == NSUserInterfaceItemIdentifier("CreatedAtColumn") {
                return item.createdAt
            }
            return nil
        }
        

        Make sure to set the column identifiers in Interface Builder for the table view columns as “TitleColumn” and “CreatedAtColumn”.

    7. Fetch the data and configure the NSTableView:
        • In your viewDidLoad method, add the following code to fetch the data and configure the NSTableView:
      
      override func viewDidLoad() {
          super.viewDidLoad()
      
          tableView.dataSource = self
          tableView.delegate = self
      
          do {
              try fetchedResultsController.performFetch()
          } catch {
              print("Failed to fetch items: \(error)")
          }
      
          tableView.reloadData()
      }
      
    8. Implement the NSFetchedResultsControllerDelegate methods:
      • Add the following methods to handle updates from the NSFetchedResultsController:
        
        
        func controllerWillChangeContent(_ controller: NSFetchedResultsController) {
            tableView.beginUpdates()
        }
        
        func controller(_ controller: NSFetchedResultsController, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
            switch type {
            case .insert:
                tableView.insertRows(at: IndexSet(integer: newIndexPath!.item), withAnimation: .slideDown)
            case .delete:
                tableView.removeRows(at: IndexSet(integer: indexPath!.item), withAnimation: .slideUp)
            case .update:
                tableView.reloadData(forRowIndexes: IndexSet(integer: indexPath!.item), columnIndexes: IndexSet(0...1))
            case .move:
                tableView.moveRow(at: indexPath!.item, to: newIndexPath!.item)
            @unknown default:
                fatalError("Unexpected NSFetchedResultsChangeType")
            }
        }
        
        func controllerDidChangeContent(_ controller: NSFetchedResultsController) {
            tableView.endUpdates()
        }
        
        
        
    9. Test the implementation:
      • You can now run your application and see the NSTableView working with Core Data and the NSFetchedResultsController. To test this, you might want to add some sample data to your persistent container and see if it’s being displayed correctly in the NSTableView.

That’s it! You now have an NSTableView working with Core Data using an NSFetchedResultsController on macOS. You can expand this tutorial to add more functionality like adding, editing, or deleting items.

Leave a Comment