Confused Development

I develop software and I often get confused in the process. I usually find the answers after a while, but a month later I can't remember them. So from now on, I will write them down here.

Monday, July 24, 2006

Cocoa Bindings to "virtual" data

Uh, this took me a while to figure out, even though it's fairly obvious now. I have the following situation:

  • I have an application which loads a bunch of plugins.
  • Each plugin has a UI, defined as a NIB file I created in Interface Builder.
  • The UI elements are bound to what I think could be called "virtual" attributes of an object (an instance of the plugin class). What do I mean by virtual attributes? The object doesn't actually have these attributes (e.g. "blogURL", "userName", ...) as data. Instead, it has one big data object ("publisherDetails"), and when it receives a message such as valueForKey: @"blogURL", the return value gets computed from the publisherDetails object.
  • Now, in IB I have e.g. bound an NSTextField to someObject.blogURL.
  • However, the publisherDetails only get set _after_ these bindings where established. As a result, the TextField shows a NULL value. No KVC (Key-Value Coding) method like setBlogURL: or setValue: bla forKey: @"blogURL" ever gets called.

Sort of complicated, but I don't have the patience to explain it any better now. Anyway, what has to happen is that the UI elements have to be notified that the virtual data they are bound to has changed. That data changes every time the big publisherDetails object is changed, so that's where we have to add some code. What we have to do is call the KVO (Key-Value Observing) methods willChangeValueForKey: and didChangeValueForKey: for each of the virtual attributes. Here is what my setPublisherDetails: method looks like:

- (void)setPublisherDetails: (NSManagedObject *)object
{
  [object retain];
  [publisherDetails release];
  publisherDetails = object;
  
  // when the publisher details are changed, I want observers watching
  // the individual detail settings to be informed that these
  // details changed as well.
  // detailKeys is an array with the names of all the "virtual" attributes
  NSEnumerator *detailKeyEnum = [[self detailKeys] objectEnumerator];
  NSString *key;
  while (key = [detailKeyEnum nextObject]) {
    [self willChangeValueForKey: key];
    [self didChangeValueForKey: key];
  }
}

Now each time the big publisherDetails data gets set, the UI elements get notified that the "virtual" attributes which they are bound to also changed. Thus they will update themselves neatly!

2 Comments:

At 7:38 am, Blogger Peter Hosey said...

I suggest using an NSObjectController here. Then you need only change the content object of the NSObjectController ([myObjController setContent:object]), and that will handle the KVO notifications.

 
At 4:18 am, Blogger Peter Hosey said...

Yargh—I didn't understand your problem correctly the first time I read it. Sorry.

The correct solution is much simpler. In +initialize, call [self setKeys:triggerChangeNotificationsForDependentKey:], passing the array of all of those virtual attributes and the @"publisherDetails" key. Then you don't need that loop.

 

Post a Comment

<< Home