NSSegmentedControl and Core Data.

It’s not possible to directly hook up an NSSegmentedControl (or NSSegmentedCell) to a Core Data controlled NSArrayController (or any, for that instance). I wanted to be able to use one of these controllers to allow the user to select one, many or all items from the array.

To define the behaviour a little bit more: You can select the first item in the control (which will be called “All”), and it will select or deselect all of the other items, depending on what it’s state becomes. If you click on another segment, then it will toggle the selection of that segment. If all segments are ON after pressing a segment, then the first segment must also display ON, otherwise it will be OFF.

I did it by creating a new class: SegmentController. (I know, I should put on a prefix…)

SegmentController has two required IBOutlet variables, which refer to the NSSegmentedControl, the NSArrayController, and a property NSMutableIndexSet, which stores, sets and gets the selection indexes (I think this should be indices, but I’ll stick with Apple’s convention). I also have a sortDescriptors array, so that this object can sort the array controller.

There are a couple of limitations, which I might look at how I deal with - for instance, the NSSegmentedControl must have exactly one more segment than the NSArrayController has items. I’m using it to select one or more days, and this number doesn’t change. It should be possible to dynamically create the right number of segments, and populate them with values (I even have a readonly field called shortName set aside in my Day objects for this), but at awakeFromNib the NSArrayController is still empty, and I can’t figure out a nice neat way to force a fetch.

Hooking up the elements in Interface builder is easy. Create the NSArrayController, and set that up however you need to. Do the same with the NSSegmentedControl - at this stage you’ll need to put values in each of the segments. Now, create a new instance of SegmentController (you might need to add the class files to your XCode project first). Connect the two outlets up to the required objects.

Now, in the Bindings Inspector, connect up you’ll want to make the Selection Indexes parameter point to the selectionIndexes model key of your Segment Controller object.

Finally, make the selector of the NSSegmentedControl point to the selectSegment: action of the Segment Controller.

I did notice a slight delay between deselecting one segment and the “All” segment deselecting. A simple optimisation - moving the call to segmentCount out of the loop test and into a local variable made it much smoother.

That should probably do it. It might need a bit of tweaking. You should be able to have multiple instances of this in your project, if it is required. It’s been designed that you’ll need a seperate one for each Segmented Control, as it refers to the objects it is connected to, rather than the sender of the message.

My source code is here: SegmentController.zip

Using moGenerator as part of the Build Process (XCode)

Using Core Data is awesome.

moGenerator makes it even better, as you can have Entitites defined as being of a particular class, and this code generator creates two classes for each Entity - a machine-readable one which is updatable by running the script again, and a human readable one that you can safely edit - it won’t be changed once it has been created.

You can easily make this tool a part of you build process.

First, create a new directory inside your project folder, I called mine MO. Then, cd to that directory, and run:

$ mogenerator -m ../*_DataModel.xcdatamodel

Then add the folder MO to your project.

Now add a new build phase script, and put into it:

cd MO
mogenerator -m ../*_DataModel.xcdatamodel

It’s not quite perfect - if you add a class, it won’t be added to your project automatically. You might be able to get around this by including the -includem switch, but then you won’t be able to have the .m files located in your project, else you will get duplicate symbol errors.

To-Many Relations in Core Data using Cocoa Bindings

I’m currently learning stacks (and maybe developing a useful application along the way) about Core Data and Cocoa Bindings.

I managed to hook up all of the “first level” Entities to UI elements, but was struggling a bit figurng out how to get the to-many relationships to work.

To make all elements of the type Person appear in an NSTableView, you can create an NSArrayController, set it to the Entity type, and put Person in the relevant field. Then all people will appear in the list.

If you have a manager, who might supervise zero or more people, then how do you get a sub-set of People. More specifically, how do you get a just the other side of a To-many relationship.

I thought I was going to have to resort to actually writing some code, and use a Predicate, or something like that. But there is an IB only method.

You can create an NSArrayController, and as well as putting in the Entity type (and hooking it up to the Managed Object Context), just put that it gets it’s Content Set from another object - in this case it would be People.selection.supervises (or whatever it is called).

This isn’t quite flawless - if you just hook up a button to the “add:” outlet, then it doesn’t quite add properly - the reverse of the attribute is not set. I had to make up an IBOutlet in my window controller that creates a new component, and sets the relationship up.

Which meant I ended up having to write two lines of code. For each relationship.