I Have an NSArrayController bound to a NSUserDefaults controller, with setSelectsInsertedObjects set to YES in Interface Builder, but when I click Add, the previously select object gets unselected, instead of selecting the newly added object.
What am I missing?
How are you binding them? If it is through NSArrayController's 'content' binding, then I believe it tries to bind the selectionIndexes to the same object. This class (NSIndexSet) does not work with NSUserDefaults (I have no idea why, but I've had the same problem in the past - I think it has something to do with it's object lifecycle; it gets initialized as empty and then adds indexes or something). What setSelectsInsertedObjects is doing is just automatically updating the selectionIndexes when a new object is added, and basically your NSUserDefaults controller is messing that up. I'm not sure where it is, but I think if you hunt around NSArrayController's bindings you will find one for selectionIndexes (or something related) that was automatically bound to NSUserDefaults for you; if you uncheck that, things should work.
That's pretty much what selectsInsertedObjects means, as I understand it. When the user adds a new item, the new item is selected, replacing the previous selection.
If you want different behavior, you could extend NSArrayController or create your own controller class that uses NSArrayController as a delegate, perhaps based on NSProxy. I believe you'd need to override add: to:
save the current selection
call the parent add:
merge the current selection with the saved selection
set the selection to the merged selection
However, I don't know enough about NSArrayController internals to say whether this would work.
Related
I have a document-based Cocoa application in OS X with a Core Data backed NSArrayController bound to the columns of an NSTableView and "Add" and "Remove" buttons.
This all works fine, objects added and modified using these bindings get added to the undo stack and save to files as expected.
However when I .addObject() programmatically, it is reflected in the table (and therefore, it would seem, the NSManagedObjectContext), but not added to the undo stack, or saved to files.
What am I missing? Some setting in my NSArrayController? Or some other call after .addObject()?
I have heard "Handles Content As Compound Value" mentioned in relation to similar problems, but this seems to be when using the Content Array binding, which I am not.
Are you calling .addObject() on the array that your NSArrayController is managing? That won't work because you are bypassing Core Data.
To insert programmatically, use insertNewObjectForEntityForName(inManagedObjectContext). Then the array controller will see the change. Or call add() on your array controller, which is what your Add button is doing. But insertNewObjectForEntityForName() is a better choice, because it will work independent of whether you have an active NSDocument.
I am connecting an arraycontroller (myArraycontroller) in Interface Builder to an array (fileDictionariesArray) consisting of dictionaries. This works fine, but when I try to access and enumerate over the contents of the arraycontroller [myArrayController arrangedObjects] I get nil in content until I add the content programmatically to my arrayController like this:
[myArrayController addObjects:fileDictionariesArray];
After I have done that I can loop over all the contents. The problem is that the array (fileDictionariesArray) has been added two times. One time through IB binding and one time through addObjects. I just can't access the arrayContoller until I set the content programmatically. I can delete the content of the arrayController and then set it programmatically again like this:
[myArrayController setContent:nil];
[myArrayController addObjects:fileDictionariesArray];
Which gives the correct number of items in the arrayController, but it does not seem like the correct way to do this. I would appreciate it of anyone could give me a hint on how to access my arrayController through the arrangedObjects array without adding the content two times.
Thank you for your help. Cheers, Trond Kristiansen
Based on your post and the comments between you and Bavarious, I'm convinced he shouldn't have deleted his answer. He'd asked you where you were calling this code (from -awakeFromNib?). This is an essential question to answer.
If you call this from -awakeFromNib, the next question is "of what object in the nib? the owner? some other controller? what is the? a document?"
The thing is the array controller likely doesn't have time to observe the "changes" to the array before you call your code. That's why adding the content forcibly results in its being added twice (because after you're done, the array controller observes its content array and pulls in the content ... again ... at which point you've doubled the array's contents).
If you're doing immediate startup stuff, it might be best to operate directly on the array. There's nothing "wrong" or "dirty" about that, so long as you leave responsibility with the array controller after startup is complete.
It's hard to get any more specific because I'm just guessing at what you might be doing. Amend your question to include a more complete description of how/where your array controller fits into the architecture, what specifically you're doing to the array at startup, etc. Missing any of this information forces too many guesses.
I'm really having trouble getting a Cocoa Table View cell to send action messages.
At the most basic level, in IB there is an action assigned for the NSTextViewCell object, and after editing and pressing Return nothing happens.
So I have an IBOutlet hooked up to the NSTextViewCell, and have been experimenting with NSActionCell messages to it. But the Table View seems to pretty much just ignore them.
I've also tried subclassing NSTextViewCell, but the methods I'm seeing all look like they want to pass values to the object from somewhere, not return a value from inside the object to configure its behavior.
I'm pretty new to programming and Cocoa -- can someone explain each thing that needs to be overridden and how and where to do it?
AFAIK, the cells in an NSTableView won't send action messages out to your application, they're sent to the NSTableView so it can update its data. NSTableView itself tries to be pretty clever and update your data directly, rather than just telling you something changed, so depending on what you're trying to do and what the data source for the table is, you have a few options.
If you're using an NSTableViewDataSource object to populate the table, it's simple; just implement tableView:setObjectValue:forTableColumn:row: and the NSTableView will call that every time something is edited.
If you're using Cocoa data binding (for example, using an NSArrayController to bind an array of objects to the table,) then as long as everything is wired up correctly, the data should just automagically get updated in the source objects when the table is edited. If you need to take special action, then you can do whatever you need to in the property setter of your data class.
I haven't tried it yet, but could work...
NSCell *cellYouWant = [tableView preparedCellAtColumn:tableView.clickedColumn row:tableView.clickedRow];
I have an NSTableView and I want to do something whenever the selectedCell element changes.
So, my table view is called tableView, and this is what I want to observe:
[tableView selectedCell]
I tried using key-value observing, but that didn't seem to work, or maybe I was doing it wrong. Any ideas?
Most properties of Cocoa's own classes are not observable. If a property is observable, the documentation for it will explicitly say so; if the documentation doesn't say a property is observable, assume it isn't.
Furthermore, properties that don't exist are doubly not observable. The documentation for NSTableView and NSOutlineView both mention no method named “selectedCell”. You should assume there isn't one.
If you want to know when the user selects a different row, be the table view's delegate; it sends delegate messages for that, if you'll respond to them.
NSTableView will use one and only one dataCell object for each column. selectedCell is the wrong way. You can use selectedColumn to get the selected column and then ask for its dataCell.
And: I guess you are searching for NSTableView delegate methods
tableViewSelectionDidChange: and tableViewSelectionIsChanging:
I'm trying out some MacOS programming and having some trouble understanding how bindings work with an NSPopupButton. I'm interested in binding to an NSDictionaryController (I don't think I need an intermediate NSArrayController but if that is the best way, I'm open to it).
I've created a controller object that has a property 'db' which has a property 'species' which is an NSMutableDictionary. The 'species' dictionary has ID's for keys and Species objects for values. Species objects have a description property. In InterfaceBuilder, I've created MyController, NSDictionaryController, and an NSPopupButton. I would like to populate the popup with Species.descriptions. When selected, I need access to the corresponding ID.
I've setup the NSDictionaryController to bind 'Content Dictionary' to MyController with Model Key Path 'db.species'. With NSPopupButton, so far I've bound 'Content Values' to NSDictionaryController with controller key 'arrangedObjects' and Model Key Path set to 'value.description'.
This seems to work getting the list populated. My main question is what the best way to wire up the selection is. Ideally, I would like to wire selection to the NSDictionaryController so that I can use the NSDictionaryController to access the selection. One reason for this is so that I can wire other controls to the NSDictionaryController to see the current selection. If not, should I wire to a property in MyController or something? Just looking for the best practices. I would like as much to be through the Interface Builder mechanisms so that I can easily reuse the model and controller design in another application with a different view.
Update using Brian's answer as guidance:
NSPopupButton:
bind Content to NSDictionaryController->arrangedObjects->value.description
bind Content Objects to NSDictionaryController->arrangedObjects->key
bind Selected Index to NSDictionaryController->selectionIndex
bind NSDictionaryController->db.species
Everything seems to work. I can grab the object from the controller with [[[controller selectedObjects] lastObject] value]. It's in an array of selected objects with key, value pairs, I believe.
I've never tried this with an NSDictionaryController, but I think you would want to bind the contentObjects of the pop-up to the dict controller's "arrangedObjects.key" and the selectedObject binding to the dict controller's "selection" key. The contentObjects binding would specify the IDs as being the underlying objects represented by each menu item. Then when an item is selected from the pop-up, the selectedObject binding would set the ID corresponding to that menu item as the selection of the dict controller.
I would like to populate the popup with Species.descriptions. When selected, I need access to the corresponding ID.
Bind content to the dictionary controller's arrangedObjects.value (don't include description—the pop-up button will do that for you) and contentObjects to the dictionary controller's arrangedObjects.key.
For more info, see NSPopUpButton in the Cocoa Bindings Reference.
(I notice it describes content as “An NSArrayController instance …”. Dictionary controllers are array controllers, so that shouldn't be a problem, but binding to a property of the controller may be. Something to watch out for.)