Binding NSComboBox

I’ve been working on an app that has a few NSComboBoxes. I wanted the NSComboBoxes to work as expected, allowing the user to choose an item from the drop down list or enter a new item directly into the text field. I ran into a few problems using bindings in this situation and found that just setting the bindings up in interface builder was not enough as it didn’t seem to automatically add any new items typed in to the associated NSArrayController.

I had the following set in the bindings for each NSComboBox:

Value Selection
![contentbindings](http://www.geeklane.com/blog/images/contentbindings.png)

The content binding makes sense, I’m making the items in the NSComboBox come from the Species Controller.

![contentvaluesbinding](http://www.geeklane.com/blog/images/contentvaluesbinding.png)

The contentValues binding also makes sense, The actual values displayed in the list should be the botanicalName from the Species Controller.

![valuebinding](http://www.geeklane.com/blog/images/valuebinding.png)

The value binding handles what should be selected, so I’m telling it to look at what the Main Array Controllers selection is and use the species botanicalName as the value.

This works fine for values already in the list. Users can choose an item and it gets assigned to the relevant object. It doesn’t work though for cases when the user wishes to type in a new item, as you would expect for an NSComboBox.

It took me some time and a lot of internet browsing / reading to find a solution to the problem. It involves manually adding the new typed in item to the necessary NSArrayController.

I’ve used two NSComboBox delegate methods in order to solve the problem.

- (BOOL)control:(NSControl *)control isValidObject:(id)object

- (void)comboBoxSelectionDidChange:(NSNotification *)notification

The first is just a generic NSControl delegate which, in the case of an NSComboBox, is called whenever new items are manually typed in to the NSComboBox. As the name suggests you can use this to determine whether the entered ‘object’ is valid for the NSControl.

So, in this method I have something as follows:

if([control tag] == 1) //species combo box
{
BOOL speciesIsOK = FALSE;
NSManagedObject *species;
NSString *speciesName = [NSString stringWithString:object];
[speciesName retain];

NSEnumerator *e = [[speciesController arrangedObjects] objectEnumerator];
id speciesObject;

// check if the value that was entered is actually in the list already or not
while ( (speciesObject = [e nextObject]) ) {
if ([[speciesObject valueForKey:@"botanicalName"] isEqualToString:speciesName]){
speciesIsOK = TRUE;
species = speciesObject;
break;
}
} //end while

// Existing species not found, User is creating a new species
if (speciesIsOK == FALSE){
species = [NSEntityDescription insertNewObjectForEntityForName:@"Species" inManagedObjectContext:managedObjectContext];

[species setValue:speciesName forKey:@"botanicalName"];
[speciesController addObject:species];
speciesIsOK = TRUE;
}
// assign the value to the main ‘bonsi’ controller
unsigned int selectionIndex = [bonsaiArrayController selectionIndex];
NSManagedObject *bonsai = [[bonsaiArrayController arrangedObjects] objectAtIndex:selectionIndex];
[bonsai setValue:species forKey:@"species"];
}

This method basically checks to see whether the entered species is already in the list, if it isn’t, it creates a new species object and assigns the entered text to it. It then adds the item to the main bonsai array controller.

The second delegate method is used to set the species value if the user just chooses another one from the list.

if([[notification object] tag] == 1) //species combo box{
NSEnumerator *e = [[speciesController arrangedObjects] objectEnumerator];
id speciesObject;
while ( (speciesObject = [e nextObject]) ) {
if ([[speciesObject valueForKey:@"botanicalName"] isEqualToString:[speciesComboBox objectValueOfSelectedItem]]){
unsigned int selectionIndex = [bonsaiArrayController selectionIndex];
NSManagedObject *bonsai = [[bonsaiArrayController arrangedObjects] objectAtIndex:selectionIndex];
[bonsai setValue:speciesObject forKey:@"species"];
break;
} //end if
}//end while
}

Implementing these two delegate methods means we have covered both possible ways for NSComboBoxes to be set and also ensures that any changes are saved to the relevant NSArrayControllers.

This seems like it would be a common problem, but it actually took me a fair while to get it working. Hopefully with me posting it here someone else might get some help from it. Also, if anyone has suggestions on making this better or a more efficient way of doing it then feel free to let me know.

Add a Comment

One Comment

  1. Hi there, I am trying to do the same thing with an NSComboBoxCell. I’m not entirely sure where to put those two delegates though. Being a .NET developer I am still feeling my way around Cocoa’s MVC world. Any pointers would be greatly appreciated.

    Thanks,
    Dany.

Add a Comment