views:

370

answers:

3

This is a Cocoa n00b question - I've been programming GUI applications for years in other environments, but now I would like to understand what is "idiomatic Cocoa" for the following trivialized situation:

I have a simple custom NSView that allows the user to draw simple shapes within it. Its drawRect implementation is like this:

- (void)drawRect:(NSRect)rect
{
    // Draw a white background.
    [[NSColor whiteColor] set];
    NSRect bounds = [self bounds];
    [NSBezierPath fillRect:bounds];

    [[NSColor blackColor] set];

    // 'shapes' is a NSMutableArray instance variable
    // whose elements are NSValues, each wrapping an NSRect.
    for (NSValue *value in shapes)
    {
        NSRect someRect;
        [value getValue:&someRect];
        [self drawShapeForRect:someRect];
    }

    // In addition to drawing the shapes in the 'shapes'
    // array, we draw the shape based on the user's
    // current drag interaction.
    [self drawShapeForRect:[self dragRect]];
}

You can see how simple this code is: the shapes array instance variable acts as the model that the drawRect method uses to draw the shapes. New NSRects are added to shapes every time the user performs a mouse-down/drag/mouse-up sequence, which I've also implemented in this custom view. Here's my question:

If this were a "real" Cocoa application, what would be the idiomatic way for my custom view to update its model?

In other words, how should the custom view notify the controller that another shape needs to be added to the list of shapes? Right now, the view tracks shapes in its own NSMutableArray, which is fine as an implementation detail, but I do not want to expose this array as part of my custom view's public API. Furthermore, I would want to put error-checking, save/load, and undo code in a centralized place like the controller rather than have it littered all over my custom views. In my past experience with other GUI programming environments, models are managed by an object in my controller layer, and the view doesn't generally update them directly - rather, the view communicates when something happens, by dispatching an event, or by calling a method on a controller it has a reference to, or using some similarly-decoupled approach.

My gut feeling is that idiomatic Cocoa code would expose a delegate property on my custom view, and then wire the MyDocument controller object (or another controller-layer object hanging off of the document controller) to the view, as its delegate, in the xib file. Then the view can call some methods like shapeAdded:(NSRect)shape on the delegate. But it seems like there are any number of other ways to do this, such as having the controller pass a reference to a model object (the list of shapes) directly to the custom view (feels wrong), or having the view dispatch a notification that the controller would listen to (feels unwieldy), and then the controller updates the model.

+1  A: 

there are several similarities between your code and an NSTableView, so I would look at maybe using a data source (similar to your delegate) or even perhaps bindings.

cobbal
You're right, a `MyCustomViewDataSource` seems more appropriate than a `MyCustomViewDelegate` for a situation like this. And bindings seem like the best fit of all.
erikprice
+4  A: 

Having a delegate is a cromulent way to do this. The other way would be to expose an NSArray binding on the view, and bind it to an array controller's arrangedObjects binding, then bind the array controller's content binding to whatever owns the real array holding the model objects. You can then add other views on the same array controller, such as a list of objects in the active layer.

This being a custom view, you'll need to either create an IBPlugin to expose the binding in IB, or bind it programmatically by sending the view a bind:toObject:withKeyPath:options: message.

Peter Hosey
Embiggen is a perfectly cromulent word. I second your suggestion to use bindings for data over delegates. They're a little more work to set up, but ultimately are much easier to use.
Alex
Bindings seems like exactly the solution I was looking for. Since this is just a toy project for learning, I'll try both the programmatic- and the IBPlugin-based approaches. Thanks.
erikprice
+2  A: 

There is a very good example xcode project in your /Developer/Examples/AppKit/Sketch directory which is a more advanced version of what you are doing, but pertinent nonetheless. It has great examples of using bindings between controller and view that will shed light on the "right" way to do things. This example doesn't use IB Plugins so you'll get to see the manual calls to bind and the observe methods that are implemented.

Evan
Thanks for pointing that out, I'll take a look at this project.
erikprice