Bind custom control to NSArrayController - objective-c

I've subclassed NSTextField to create a custom control, and I want to bind a property (which is an NSArray) of my custom control to an NSArrayController. However I don't know how to propagate the array from my control to the NSArrayController. The key-path I'm using on the NSArrayController is arrangedObjects.name. For example, if I'm trying to propagate the array (#"One", #"Two", #"Three") and I simply use:
[boundObject setValue:myArray forKeyPath:#"arrangedObjects.name"]
it will set the value of each element of arrangedObjects.name to the array (#"One", #"Two", #"Three"). What I want to happen is to have the first element in arrangedObjects.name set to #"One", the second value set to #"Two", etc.
NSTableColumn does this, so I know it's possible, but I can't figure out how it is implemented.
What's the best way to achieve this?

Ok, well I think I may have answered my own question. I won't mark it as the correct answer right away in case someone can tell me a better way to do this. This way is pretty much a hack.
Anyway, I was reading about NSArrayController here: http://www.cocoadev.com/index.pl?NSArrayController which says:
If valueForKeyPath:#"arrangedObjects.name" is sent to an array controller, one (as expected) gets back an array of name values, but if another object replicates this behaviour, and the table column's value is instead bound to this object, it will display the entire array for each row.
and
You bind the value of an NSTableColumn to arrangedObjects.someKey. If you try to programmatically invoke valueForKeyPath:#"arrangedObjects.someKey" on your array controller, you'll get back the array resulting from invoking valueForKey:#"someKey" on the arrangedObjects array -- this is all fine. So one would think that NSTableColumn's value can also be bound to my objects someArray.someKey, but this will not work (on several levels).
So basically it sounds like NSTableColumn special cases its bindings for NSArrayController and arrangedObjects which is why it works the right way, and my custom control doesn't.
This doesn't seem very flexible, but it's the only way I can find to make it actually work. I implemented a special case in my bindings for NSArrayController and arrangedObjects and I was able to make it work like I wanted.
A better solution would be much appreciated, though!

Related

NSTreeController with editable contents

I've got what (I would think) is an extremely simple case where an NSTreeController is bound to an array of root objects, each of which might have a few child objects. I am using an NSBrowser to show them.
They display fine and the hierarchy is correct.
The problem is the Tree Controller is not making any of the items editable. I want to be able to edit and remove (but not necessarily add) items. canRemove, canEdit always return NO, and the NSBrowser will not edit the labels.
The Tree Controller is marked editable and the count key path is not specified. "Conditionally sets editable" is set in the binding.
I am binding to "Content Array", not "Content", since the root level of items is an array.
Just to eliminate the possibility of mutability being a factor, The array and children are mutable arrays from readwrite properties (for now).
What am I doing wrong? Is binding through an NSTreeController not the right approach here? At this point, it seems easier just to revert to using a data source delegate.
UPDATE: stupid, but probably helpful to people who don't do Cocoa UIs every day (like me), so I'm leaving this question up.
I didn't have the selectionIndex bound between the control and the controller.

Bindings vs IBOutlets in Objective-C/Cocoa

I'm going through a simple Objective-C/Cocoa program to try and learn the language and am getting a little confused about how some things are linked from the code I write to the interface builder.
For example, I have a simple NSString:
#property (assign) NSString *letters;
And in my interface builder, I have a text field and I use the text field's bindings to connect it to letters.
However, the example also has this:
#property (assign) IBOutlet NSArrayController *wordsController;
In the view I have a table that continuously changes and shows different words and those words are stored in an NSMutableArray. I suppose I can understand that I just can't bind the array to the the table because there are some more complexities. So in the Interface Builder I create an Array Controller and bind it to the table. In the Array Controller's bindings, I bind the Array Controller to the array of words.
I understand that the last thing I have to do is also bind the Array Controller to my NSArrayController object as well. I don't understand why I do this through the main controller object by making a connection between this outlet and the wordsController. In the Array Controller's bindings section there's a greyed out option, Content Object, which says "An NSArrayController that the NSArrayController treats as its content." Why wouldn't I set the binding here? What is the significance of it being an outlet and why is it different than my NSString letters?
Thanks
You are confusing bindings and IBOutlets. This is not unreasonable -- it's a lot of Control-dragging of connections and it can be hard to keep clear what's going on. Let me try to explain:
Bindings are a way to let Cocoa handle the mechanics of keeping a model (some collection of data, even something as simple as a single NSString) and a view (an object which displays on the screen) in sync. When you "bind" your NSString to a text field's value, you are asking the framework to communicate changes to the string or the text field "behind the scenes"; your object which owns the string gets notified to change the string's value when the text field changes, and vice versa.*
A similar situation applies to your mutable array, array controller, and table view. You're essentially right about the complications: the mutable array and the table view don't know how to talk to each other; the array controller stands in between and facilitates: ("Okay, tableView wants to know what to put in row i. Array, give me your object at index i." :) In the past, you would've had to write that code manually, and it looked very similar every time you did so.
That's what the bindings do. They are a way to reduce boilerplate code. The IBOutlet to the array controller gives your object a way to send messages to the array controller, if necessary. One simple example of why you might need to do this is to allow a menu item to trigger a method in the array controller; a document object or another controller might handle the action from the menu item and call the appropriate message on the array controller. You can also ask the array controller for its arrangedObjects to get, for example, the sorted and filtered version of its content array.
* One side note here is that your NSString property should almost certainly use retain, not assign. The object that contains this variable should be responsible for its memory.

NSArraycontroller access of content

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.

setSelectsInsertedObjects on NSArrayController not actually selecting

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.

When does selectedCell change?

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: