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
.xcdatamodeldfile 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.storyboardfile. - Drag an
NSTableViewfrom the Object Library to your window. Make sure to also include the scroll view. - Set up Auto Layout constraints for the
NSTableViewto fit the window. - Add two columns to the
NSTableViewand set their titles to “Title” and “Created At” respectively.
- Open the
- Connect the
NSTableViewto the code:- Open the
ViewController.swiftfile. - Add an
@IBOutletfor theNSTableViewand 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.swiftfile: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
NSTableViewDataSourceandNSTableViewDelegate:- Make your
ViewControllerconform to these protocols:class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate, NSFetchedResultsControllerDelegate { - Implement the required
NSTableViewDataSourcemethods: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
viewDidLoadmethod, 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
NSFetchedResultsControllerDelegatemethods:- 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
NSTableViewworking 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.