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.
Related
I have a plist file that holds information I need to display in an app organised as a dictionary of dictionaries. I've just started programming Cocoa so am not sure the best way to go about this. Obviously I can do it all manually, and code up the loops and add the data to the UI elements, but it seems to me that bindings and the provided controllers should let me do this more easily.
I was specifically wondering if there was a direct way (e.g. using mostly Interface Builder) to link the NSDictionary I get from reading the plist file, that itself contains further NSDictionary elements, which in turn contain name-value string pairs, to an appropriate user interface element -- probably an outline view or a browser.
Alternatively, the data would fit into a function browser type panel (like in Excel) where the top level keys are categories of functions, the next level are functions in that category, and I can just populate a text area with the lowest-level details -- i.e. the value data from the final dictionary.
I don't think you are going to be able to do this with an NSBrowser or NSOutlineView. The reason I say that is because if you are using bindings with those views you need to use an NSTreeController. NSTreeController provides the ability to specify which keys in your model indicate whether or not the current object has children objects (isLeaf) and how to access the children objects (children).
So if you are going to use one of those two views, you must be able to add additional keys and properties to your model to do so. Many times when I work with NSOutlineView and NSBrowser I find it easiest to skip bindings altogether and just use the all delegate & datasource methods. They require more code but they aren't hard to put together and sometimes I prefer them to bindings if my data model is complex or if the data is not in a format that is easily pumped through an NSTreeController.
However you could use an NSTableView by doing the following.
Create an NSDictionaryController in your NIB.
In the controller that reads in your plist, create an outlet for the NSDictionaryController and hookup the outlet using Interface Builder.
In the code that reads the plist, add an additional line of code that set's the NSDictionaryController's content to the root dictionary from the plist.
In your NIB, create an NSArrayController. Bind the array controller's "Content Array" binding to the NSDictionaryController. For the "Controller Key" binding property, specify "arrangedObjects".
Now take an NSTableView and place it in your NIB. Bind each of the NSTableColumn's "Value" bindings to the NSArrayController and for the "Controller Key" binding property, specify the key from the dictionary whose value you want to display in the table column.
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!
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 not really sure how to start debugging this issue.
I've got an NSCollectionView, whose NSCollectionViewItem prototype view itself contains an NSCollectionView (as well as an NSArrayController, to provide content to this 2nd-level collection view). Both levels of collection view work fine when the top-level view is in the main nib.
However, when I copy/paste the view (and reconnect all the appropriate bindings) to a new nib, which I load with loadNibNamed:owner:, the 2nd-level view--but not the top-level one--appears blank.
Upon some investigation, I discovered that myArrayController.arrangedObjects.#count is indeed 0. HOWEVER, the NSArray the controller is bound to (File's Owner's representedObject.quizzes), when asked for .#count, returns 2.
quizzes should indeed return 2, for I've done [testCategoryA setQuizzes:[NSArray arrayWithObjects:testQuizA1,testQuizA2,nil]];. I've tested setting the quizzes before the nibs are loaded and after. The situation is the same in both cases.
So, in conclusion, I've got 2 levels of collection views, with 2 levels of array controllers. The top level always works. But the 2nd level breaks whenever the top level isn't in the main nib file. And it seems to me that the part about the 2nd level that breaks is the binding of the array controller.
I don't even know how to start debugging in this tangled mess of nibs. Suggestions?
It sounds as though your quizzes array is either not sending KVO notifications or you're editing it in a non-KVO-compliant way (ie, "editing the array behind the controller's back").
Further, you might want to check into Indexed Accessor Methods for your quizzes array for performance reasons.
I've a mutable array that holds instances of a model object. That model object has several properties one being "name". I have no problems initialising or populating the mutable array.
I've a window with a drawer. I added a table to the drawer, the idea being that the drawer would use the table to display several instances of the model object.
I added an nsarraycontroller to the xib of the window that has a drawer. In the Array Controller Properties I've set the Object Controller to be an instance of the model class. On the Array Controller Bindings I set the Controller Content to point to the File Owner and set the Model Key Path to the name of the array.
On the table, I bind the content to the Array Controller, the Controller Key to arrangedObjects and Model Key Path to name.
My problem is that although the mutable array has been properly initialised and populated I can't see a single entry on the table on the drawer. Am I missing something here?
Two possibilities:
First: you might have bound the wrong thing (your description here is a bit ambiguous). Bind each table column's "values" to the array controller's #"arrangedObjects.propertyName" (like arrangedObjects.firstName for the First Name column, etc.). There are alternative ways to bind the whole table, but you probably aren't binding the column's values, just the table's content.
Second: it's also possible the accessor to your model object isn't KVO compliant. Make sure proper KVO notifications wrap your setter accessor for your model array. If you've #synthesize'd it, all should be well. If you've hand-coded your accessors, all might not be well. :-)
I assume you bound the tablecells table columns to the arraycontroller? I don't think Interface Builder will let you do anything else. Otherwise, it sounds like you have it configured properly.
I would recommend ibtool for troubleshooting these kinds of problems. It's a command line tool that does a text dump. You can inspect bindings in a more compact form than using the GUI in Interface Builder.