views:

51

answers:

2

Hi everybody,

I have a problem with a bound property of a custom view. The property is bound to an NSArrayController for a core-data entity.

Here's the problem:

In my view, I draw several rectangles. The positions of these rectangles are saved in an entity in core data (as NSValue wrapped NSRects). The rectangles are draggable, and when I drag a rectangle, I want the entity to be updated to the new position. But I can’t find out how to bind the arrayController back to the view.

Here is how everything is connected:

  • I have a windowController with outlets to the view and the arrayController.

  • The arrayController is bound to the managedObjectContext of the windowController and therefore to my core-data model. (That binding and the binding to the entity are established in Interface Builder)

  • The view is bound to the arrayController with an ivar „rectangleValuesArray“

That last binding is established in the windowController using:

[connectionsView bind:@"rectangleValuesArray"
             toObject:elementsArrayController
          withKeyPath:@"arrangedObjects.rectangleValue"
              options:nil];

That works so far.

Now I tried to establish the binding in the other direction with:

[elementsArrayController bind:@"arrangedObjects.rectangleValue"
                     toObject:connectionsView
                  withKeyPath:@"rectangleValuesArray"
                      options:nil];

Now when I change my ivar, the Entity should be updated (right?). But that doesn’t work - I get that from the console:

Cocoa Bindings: Error setting value for bound property arrangedObjects.rectangleValue of object <NSArrayController: 0x200027100>[entity: Element, number of selected objects: 1]: [<NSArrayController 0x200027100> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key arrangedObjects.rectangleValue.

What am I getting wrong here? Please, somebody help?

Simon

A: 

Not entirely sure I follow what you're doing there, but have you considered this approach:

  • give the view an IBOutlet NSArrayController* myArrayController ivar for your array controller (might already have something like that, I figure), and set up that connection in IB
  • omit your two manual binding calls from the window controller

Now in order to modify the contents of the array controller from within the view, you'd call [myArrayController mutableArrayValueForKeyPath:@"arrangedObjects.rectangleValue"], which should give you a proxy to that key path which you can modify at will with KVO updates.

Hope that's not entirely besides the point.

ig2r
Getting a mutable array for the `rectangleValue` property of the NSValue objects won't help him set a property of an immutable object (NSValues are immutable—note their lack of any `setWhateverValue:` methods). Switching from binding in code to binding in IB will make things easier, if he can do it, but it will not solve any of his problems.
Peter Hosey
Oh.. I overlooked that immutability bit altogether; instead, I somehow misinterpreted the error message as a complaint that the view was missing KVO-compliant accessors for that ivar. You are correct of course.
ig2r
+2  A: 

A couple of things.

First, the exception is correct: It isn't possible to set the rectangle value of any NSValue objects. They're immutable.

It sounds like you're trying to maintain parallel arrays, with the array of rectangles being one of them. Don't do that; Bindings chokes on it and it makes AppleScript support much harder as well.

What you need to do is implement model objects, and keep a single array of those. Whatever arrays you currently have, including the one of rectangles, should become properties of the model objects—each one has a rectangle, among whatever other things.

The other thing is that you don't bind the controller to the view; you bind the view to the controller. Having already done that, you're done.

You might also consider setting this up in IB instead of in code, if possible. Don't resist using IB; it is your friend.

Peter Hosey
Well, first of all, thank you very much!So what you mean is, that I create a model class for my objects and add that as property to my core-data entity?What am I going to do about the relationships of that entity? Should I implement them also in the model class?What I’m trying to do here ist to create something like OmniGraffle. I would like to create the bindings in IB, but I could not figure out how - it’s a custom view in which I draw my rectangles (well, they are little text „windows“). They are not there in IB...Or bind everything with outlets of the view?
Simon
For Core Data, your model object class should be a subclass of NSManagedObject and should be the class of the entity itself. You can set that in your managed object model. To expose your bindings in IB, you would need to write an IB plug-in containing your view. Bindings and outlets are not related.
Peter Hosey
Oh. In fact, I already had that model object class... I made an array of that, instead of the arrays for each property - just how could you see that I had more arrays than the rectangleValuesArray?!?. Anyway. It works now! Thanks!
Simon
Simple: I've done parallel arrays myself. ☺ It was Cocoa Bindings that first tipped me off that parallel arrays are Considered Harmful in Cocoa; later on, I got the MVC religion, largely following from the solution (switch to model objects) to the parallel-arrays-break-Bindings problem.
Peter Hosey