NSMatrix and NSCollectionView Bindings

After days of struggle I finally gave up on using bindings with an NSMatrix comprised of Checkboxes. There is no good (well, at least complete) info on how to do this on the web.

I got close though. At the end I’ll loop back around and explain what I did and what still isn’t working. For now, I’ll focus on what did work — bindings with an NSCollectionView.

The Apple Documentation on NSCollectionViews is very good, albeit probably a bit out of date and definitely not Swift-ified. In fact, following the instructions there pretty much gets you all the way to a working UI. The highlights:

  • Add the model for what each item in the Collection View will display. Mine is a Swift ‘Channel’ class (subclass of NSObject)
  • Add a Collection View to your window
  • Set up your Collection View item in IB. In my case:CollectionViewIB
  • Add an Array Controller to your xib
  • Set the Array Controller Object Controller class to model class and add the relevant properties (in the Attributes Inspector). In my case this looked like this CollectionViewObjectController
  • Set up the Array Controller bindings. In my case this consisted of setting the Content array to File’s Owner.channels.
  • Set up the Collection View bindings. This is probably ArrayController.arrangedObjects.
  • Set up the Collection View Item bindings. In my case this is just a checkbox. The bindings I set up were
    • Value – Collection View Item.representedObject.active
    • Title – Collection View Item.representedObject.name

I think that was everything needed in IB.

Next comes the code. Apple’s doc talks about needing to add code to make the array KVO-compliant. Either that’s not necessary in this case because of Swift, or it wasn’t necessary in my case because I’m not adding and removing Channels (except at the beginning).

Here’s my Channel class:

class Channel: NSObject {
	init(active: Bool, name: String) {
		self.active = active
		self.name = name
	}
	dynamic var active = Bool(true)
	dynamic var name = String("")
}

In my document subclass I added:

	dynamic var channels = NSMutableArray()
	let channelCount = 16

And initialized it thusly:

	override init() {
		super.init()
		
		for i in 1...channelCount {
			let ch = Channel(active: i % 2 == 0, name: "Ch \(i)")
			channels.addObject(ch)
		}
	}

I alternated the active boolean to make it obvious when the bindings were working correctly. Speaking of which, there’s that ‘Dump’ button in the window. That’s also to verify that bindings are working correctly. The IBAction to which the button is bound (in the document class):

	@IBAction func dumpChannels(sender: NSButton)
	{
		for ch in channels {
			let c = ch as! Channel
			Swift.print("\(c.name) - \(c.active)")
		}
	}

Unless I left something out in this blog it all should work properly at this point.

There is one nit, and that has to do with the size of the CollectionView and how the contained items are displayed. It’s difficult to get this right in IB, but a little web search got me this answer: you can get the contained items to arrange themselves nicely inside the Collection View by doing something like this:

	override func windowControllerDidLoadNib(aController: NSWindowController) {
		super.windowControllerDidLoadNib(aController)

		collectionView.maxItemSize = NSSize(width: 0,height: 0)
	}


OK, back to NSMatrix. In a different project I have pretty much the same Channel and Document classes. The Array Controller’s Content Array is bound to File’s Owner.channels. The Array Controller Object Controller Class is Channel, and the Keys are ‘active’ and ‘name’. The NSMatrix bindings look like this:

NSMatrixBindings

When launched, the app looks like this (so far so good):

BindCheckboxMatrixWindow

However, when one clicks on a checkbox an unrecognized selector exception occurs.

2015-12-24 07:21:15.609 BindCheckboxMatrix[25396:566126] -[__NSArrayM charValue]: 
unrecognized selector sent to instance 0x600000052270
...

I don’t know how to fix this and I’m tired of trying.

Since Collection Views do what I need, I’m just going to go with them, even though it (at least by appearance) is a little more heavy-weight.

Next up — converting this to use Core Data.

Leave a Reply

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