NSTableView backed by NSArrayController: why does setContent: work while IB doesn't?
Asked Answered
F

1

5

I am trying to implement pretty much the simplest case of binding a NSTableView to a NSArrayController, so that the NSTableView is backed by an NSArray.

Here is the setup:

  1. I have an NSArrayController whose 'Content Array' is bound to an NSArray in my app delegate.
  2. In 'Object Controller' of the NSArrayController, the class name is set to Model, the type of objects contained in the NSArray.
  3. The 'Value' of the single column of the NSTableView is bound to key 'name' of 'arrangedObjects' of the Array Controller, which is the only field of the Model class.
  4. In applicationDidFinishLaunching: of my app delegate, I initialise the NSArray, and insert some Model objects.

However, the rows corresponding to Model do not appear in the table unless I also do: [self.arrayController setContent: self.array].

Is there a way I can get this to work using bindings wired up in Interface Builder? I would have expected the fact that the NSArrayController's 'Content Array' is bound directly to the NSArray to mean that I wouldn't have to set the content programmatically. Knowing why would help me understand bindings better.

Firearm answered 30/10, 2011 at 12:47 Comment(0)
G
14

Your array controller is observing your App Delegate's "array" property. That means KVO notifications are sent only when the array object is set, not when objects are added to it. It sounds like you are using an NSMutableArray and adding objects to it, which explains why the Array Controller is not being notified of changes, because the underlying object is not changing.

The easy solution is to wrap your calls in a will/did change block like so:

[self willChangeValueForKey:@"array"];
[self.array addObject:[NSDictionary dictionaryWithObject:@"foo" forKey:@"name"]];
[self.array addObject:[NSDictionary dictionaryWithObject:@"bar" forKey:@"name"]];
[self didChangeValueForKey:@"array"];

This manually notifies observers that there has been a change to the "array" property.

Long answer: You're doing it wrong. The whole point of having an array controller is to shift the work of managing the array to the controller class itself, so it manages the underlying array, sends out the right notifications, maintains state, etc. without you having to sweat the implementation details. A better solution would be to unhook the content array binding and just add objects to the array controller directly like so:

[arrayController addObject:[NSDictionary dictionaryWithObject:@"foo" forKey:@"name"]];
[arrayController addObject:[NSDictionary dictionaryWithObject:@"bar" forKey:@"name"]];

This works because the array controller manages its own array internally.

The best solution is to use Core Data. NSArrayController is designed to be used with it. You also get a whole bunch of things for free, like persistentce, undo support, object relationsips, and the ability to add objects without writing code just by calling add: on the array controller directly from your UI controls.

Gangboard answered 30/10, 2011 at 15:54 Comment(2)
Thanks. I wasn't clear on whether NSArrayController simply manages an array, or is a proxy for one. From your answer, it seems it's the latter.Firearm
It is absolutely okay to have that binding and it's not wrong. Yes – the array controller handles the arrays content. But what if you want to save / restore the objects? This is not done by the array controller (unless binding to NSUserDefaults). When using the binding and your KVO solution you can save on window on close and restore on open a window, e.g. – how handy!Penetrating

© 2022 - 2024 — McMap. All rights reserved.