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.
-
- 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.
- 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.
- Open the
- 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.
- Open the
- Connect the
NSTableView
to the code:- Open the
ViewController.swift
file. - Add an
@IBOutlet
for theNSTableView
and connect it to the table view in the storyboard:@IBOutlet weak var tableView: NSTableView!
- Open the
- 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.
- Import the necessary frameworks at the top of your
- Conform to
NSTableViewDataSource
andNSTableViewDelegate
:- 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”.
- Make your
- Fetch the data and configure the NSTableView:
-
- In your
viewDidLoad
method, add the following code to fetch the data and configure theNSTableView
:
- In your
override func viewDidLoad() { super.viewDidLoad() tableView.dataSource = self tableView.delegate = self do { try fetchedResultsController.performFetch() } catch { print("Failed to fetch items: \(error)") } tableView.reloadData() }
-
- 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() }
- Add the following methods to handle updates from the
- Test the implementation:
- You can now run your application and see the
NSTableView
working with Core Data and theNSFetchedResultsController
. To test this, you might want to add some sample data to your persistent container and see if it’s being displayed correctly in theNSTableView
.
- You can now run your application and see the
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.