NSCollectionView Bindings with Core Data

Previously I wrote a blog about using an NSCollectionView with bindings. Today I did the same thing but used Core Data for the data storage.

As before, the underlying data is a “Channel”. In this case it’s a Core Data Entity instead of a KVO/KVC compliant class, and it has 3 Attributes:

  • active (Boolean)
  • index (Int16)
  • name (String)

Unlike the ‘Channel’ class, there are 3 properties. The reason for this is two-fold:

  • Core Data sets of entities are unsorted, so I needed the index for sorting
  • I wanted to display a name that was more than just the index number.
    • Here’s the Document in its entirety:

      class Document: NSPersistentDocument {
      
      	@IBOutlet weak var arrayController: NSArrayController!
      	@IBOutlet weak var collectionView: NSCollectionView!
      	
      	override init() {
      	    super.init()
      		
      	}
      	
      	func setupNewChannelArray() {
      		var count = 0
      		let fetchRequest = NSFetchRequest(entityName: "Channel")
      		if let moc = self.managedObjectContext {
      			var result:[NSManagedObject]?
      			do {
      				result = try? moc.executeFetchRequest(fetchRequest) as!
      						[NSManagedObject]
      				Swift.print("result \(result!.count) channels")
      				count = result!.count
      			}
      		}
      		
      		if (count == 0) {
      			for i in 1...channelCount {
      				let item = 
      				NSEntityDescription.insertNewObjectForEntityForName(
      					"Channel", inManagedObjectContext: 
      					self.managedObjectContext!)
      				item.setValue(i, forKey: "index")
      				item.setValue(1 == i % 2, forKey: "active")
      				let s = "Ch \(i)"
      				item.setValue(s, forKey: "name")
      			}
      		}
      	}
      
      	override func windowControllerDidLoadNib(aController: NSWindowController) 
      	{
      		super.windowControllerDidLoadNib(aController)
      		
      		let sorter = NSSortDescriptor(key: "index", ascending: true)
      		let sortDescripters = [sorter]
      		arrayController.sortDescriptors = sortDescripters
      		
      		setupNewChannelArray()
      		collectionView.maxItemSize = NSSize(width: 0,height: 0)
      	}
      
      	override class func autosavesInPlace() -> Bool {
      		return true
      	}
      
      	@IBAction func dumpChannels(sender: NSButton)
      	{
      		if let moc = self.managedObjectContext {
      			let fetchRequest = NSFetchRequest(entityName: "Channel")
      			let sorter = NSSortDescriptor(key: "index", ascending: true)
      			fetchRequest.sortDescriptors = [sorter]
      			var result:[NSManagedObject]?
      			do {
      				result = try? moc.executeFetchRequest(fetchRequest) as!
       						[NSManagedObject]
      				Swift.print("result \(result!.count) channels")
      				for channel in result! {
      					let name = channel.valueForKey("name") as! String
      					let active = channel.valueForKey("active") as! Bool
      					Swift.print("\(name) - \(active)")
      				}
      			}
      		}
      	}
      
      	override var windowNibName: String? {
      		return "Document"
      	}
      
      	let channelCount = 16
      }
      

      The most awkward piece of this has to do with the setupNewChannelArray() function. I need to set up an array of 16 channels for every new document, but I can’t just add the 16 channels in init(), because this will cause 16 channels to be added every time a document is opened. At init() time I also can’t find out whether there are existing channels in the document; some part of the core data instantiation hasn’t happened yet. So I put this check in windowControllerDidLoadNib(), which I’m sure is not the right thing to do. When I’ve figured out how to get defaults properly set up in my document with Core Data I’m sure I will blog about it. For now the code works.

      Other fun facts about windowControllerDidLoadNib():

      • In order to get the checkboxes to display in the right order in the Collection View the Array Controller needs a sort descriptor:
        	let sorter = NSSortDescriptor(key: "index", ascending: true)
        	let sortDescripters = [sorter]
        	arrayController.sortDescriptors = sortDescripters
        
      • In order for the checkboxes to place themselves reasonably in the Collection View the following line is needed:
        	collectionView.maxItemSize = NSSize(width: 0,height: 0)
        

      The dumpChannels() IBAction method probably isn’t up to snuff. One has to deal with exception handling and I hurried through that.

      OK, now for the Core Data bindings part. There is of course an Array Controller in xib, and its ObjectController settings are:

      ArrayCtlrObjCtlrCoreData

      And in the bindings section:

      ArrayCtlrMOC

      I think that’s everything that’s much different from using bindings without Core Data. Everything on the UI side of the Array Controller is pretty much the same:

      • The Collection View’s Content is bound to Array Controller.arrangedObjects
      • The Collection View’s Item is a checkbox whose
        • Value is bound to Collection View Item RepresentedObject.active
        • Title is Collection View Item RepresentedObject.name

      Here’s the UI as setup in IB:

      CBArrayCoreDataIB

      The application window when running:

      CBArrayCoreDataWindow

Leave a Reply

Your email address will not be published. Required fields are marked *