And an answer: What to do if your table view shows the whole array in every row
Before writing my previous post, I had asked some people on IRC for help, including David Smith, who is a fellow Adium developer. He remembered a solution, but uncertainly. One thing he said, though, proved relevant: “You have two NSTableColumns; which one is it [NSTableView] creating the content binding from?”
David was referring to the content
binding of NSTableView, which I had dismissed as an artifact of an earlier design of NSTableView, or perhaps a simplified path for those people who had only one column in their table view. (I saw the mention in the docs that the table view binds it automatically to the value of one of its columns’ value
bindings, but didn’t make any connections then.)
This latter explanation made the content
binding incompatible with my app, since I had two parallel arrays of NSString
s (one array contained keys, and the other contained values). So I continued looking for a solution, and eventually gave up and wrote the post.
After I published it, Scott Anguish posted a comment on it:*
you can’t have simple arrays of strings as the content of an array controller. it doesn’t work. You need to have an array of objects of some sort that have an attribute, even if they’re just NSDictionaries with a single attribute called “string”.
I don’t believe that NSArrayController
has a special case for NSString
, but Scott’s comment got me thinking. I added a category to NSString
that added a -selfString
method, then added that name as the model key path in the table columns’ value
bindings. I was wondering, you see, whether this would be enough to satisfy NSTableColumn
and NSArrayController
.
It still didn’t work. At this point, I had the idea to fire up F-Script Anywhere and have a look around. I did that, and was digging down to the table columns when I noticed this:
That’s FSA’s object browser, showing the table view. So the table view’s content
binding was bound to the keys array controller, with a model key path of lastMessageKeys
.
That’s why the keys were showing up fine—they were in the array that was in the table view’s content
binding. The values array was a different array with a different array controller, so NSTableView
threw up its hands and simply showed the entire array’s description.
This is the point that Scott came close to: NSTableView
expects you to have one array of model objects—no more. My parallel arrays of NSString
s do not qualify, because only one of the array controllers can be the value of the table view’s content
binding.
So what role do the columns play? Well, each row in the table view (and by that, I mean *the whole row*) corresponds to one of those model objects. The value in each column is then determined by the model key path in the value
binding of the corresponding NSTableColumn
.
Thus, all the NSTableColumn
s’ value
bindings must be bound to the same array controller and the same controller key, and each column should be bound to a different model key path. Your columns’ value
bindings should look like this:
Column “Foo” | Column “Bar” |
---|---|
Bind to: Your one array controller | |
Controller key: arrangedObjects (or whatever) | |
Model key path: foo | Model key path: bar |
In my case, of course, this required creating a new model class for a key-value pair, to replace the parallel arrays of NSString
s. This is a good thing, since it brings conceptual purity to my design. Remember, Cocoa is designed around MVC; I didn’t have enough M in my app, which was the problem.
So, in summary, here’s the fix for an NSTableColumn
that shows the entire array in every row:
- Replace your evil parallel arrays with a model class, and a single array of instances of that class.
- Delete all but one of your array controllers, since you now have only one array. Update the remaining controller’s model key path to point to your new array (I assume you renamed it).
- Bind all of the table columns to your new One True Array Controller, distinguishing the columns only by the model key path, which identifies which property you want the column to show for each model object.
Many thanks to both David and Scott for their help in discovering the root of the problem.
* Scott posted his comment on the previous post with no last name; he identified himself in a comment on this post. ↶
December 9th, 2007 at 13:03:32
OK, first I clarified my last name… :-)
No, there is nothing special about an array of strings failing. an array of any simple object type will fail. You need to have an array of model objects that support attributes. From there you can delete all the extra stuff array/arraycontrollers. I figured once you got over the hump of not being able to use simple arrays, you’d get there. :-)
And if you feel there is a need for a simple tableview and bindings example in the Cocoa Bindings documentation file a bug _now_ if you can.
December 9th, 2007 at 17:22:14
Right. That’s what I figured out: The columns need to all specify various attributes of a single array of objects.
They could be
NSString
s, if you wanted to have two columns bound tolength
anddescription
. They could beNSCalendarDate
s, which gives you rather more options, but those are “discouraged” since Leopard. They could beNSURL
s. (This should all be fine as long as the objects are immutable; mutable objects will fail because they are not guaranteed to mutate in the KVO-compliant ways, which may cause the data in your views to become stale.) For pretty much anything else, you need to roll your own model class.The point that I learned is that you must have exactly one array of model objects (whatever their class)—no more.
mmalc has multiple examples. The problem that I had was that no documentation that I’ve seen has emphasized the importance of exactly one array of model objects.
I probably should file a bug about that, once I pin down a good place for Apple to put such a statement.
December 9th, 2007 at 23:24:53
But, sometimes it is useful to be able to use
NSString
s as values. I added a similar function but called it-string
, to matchNSMutableString
‘s-setString:
method. This was for an app that really did only need a one-column table view. (Of course, yes,-description
does work as well, but I wanted editable strings. For some reason. Hmm, maybe I was breaking MVC there.)February 21st, 2008 at 00:14:35
You could always write your own controller to correlate multiple arrays into keys.
OR
Why not just use an object if you have two arrays that perfectly match up and just have an array of that object?
February 21st, 2008 at 00:34:30
Eric Anderson:
Sure. I’d like to see such a solution, but it doesn’t scale unless you can make the binding variadic in IB (like enabled1, enabled2, etc.). That would require an IB plug-in; I don’t know how much easier it is on Leopard, having not yet tried it, but doing it in Tiger is a PITA.
That’s what I suggested: “In my case, of course, this required creating a new model class for a key-value pair, to replace the parallel arrays of
NSString
s.”