On 20 Jan 2009, at 5:24 am, I. Savant wrote:

On Jan 19, 2009, at 12:57 PM, Paul Bruneau wrote:

I'm confused by it, since I have been directed in the past by people who know that The Cocoa Way is to have objects know how to draw themselves--via a category if desired.

Is there differing opinion on this? (apparently there is...)

This is tough to generalize. There is not only differing opinion, but also plenty of room for debate. :-)

In the Sketch example, there are different shapes (including "text"), each of which handles drawing somewhat differently. I suspect the decision not to put this into the canvas view is more a matter of organization than architecture. I happen to agree with the decision.

There's nothing about the MVC design pattern that demands all drawing code belongs in the view. In the particular case of a drawing application where the shapes to be drawn *are* the data model, how to draw a shape could be viewed as "business logic" for the model.


My design follows this approach - the objects draw themselves, because, as you say, the objects *are* the data model. Where an object "carries" additional data that isn't directly part of the drawing, it does so by referencing a separate structure which is handled through conventional controllers and other views as necessary.

Since all of this is handled through DrawKit (http://apptree.net/drawkitmain.htm ), you can download it and take it apart if you like - no part of the data model of my app that is not part of the business of rendering objects is called at drawRect: time. If I gave that impression I didn't intend to! However the drawing methodology itself is fairly complex, involving as it does multiple layers, different kinds of objects within a layer, and each object having different methodologies for editing its geometry (e.g. editing the control points of a path versus changing its bounding rect, etc) and the visual appearance of each object handed off to a "style" object that is built up from lists of stroke, fill, gradient, image, text and other operations. The point is that it's very versatile and powerful. I'm also working on making the drawing storage scalable, which adds a further degree of complexity, since storing tens of thousands of objects in a simple array starts to become inefficient (to clarify - nothing is drawn that doesn't need to be drawn according to the view's update rectangles, but at some point you need to decide which objects are affected by an update. Iterating a long list and testing every object is inefficient, so I'm experimenting with BSP storage where given an update region a much smaller subset of objects can be instantly returned).

It's also, for the most part, robust - and getting more so. Where different objects are involved in the rendering process, I'm introducing formal protocols in place of informal ones so that non- conformance is easier to detect. This has already realised benefits of eliminating a couple of places where the wrong objects ended up getting accidentally passed to rendering code which was then throwing exceptions - so the proper handling of try/catch is only one strategy among several for making the drawing architecture as solid as possible.

Incidentally, if this seems like overkill, a glance at the larger screen shot here might help: http://mapdiva.com. This shows what I'm working on and why simple handling of stroke/fill a la Sketch just doesn't cut it! (and even this screenshot barely scratches the surface of what's possible). I would also mention that currently the drawing time is still hugely dominated by Core Graphics, so it's not like my drawing approach is causing poor performance.


IMHO objects that can draw themselves should be NSCell sub-classes or
NSView sub-classes (possibly using some primitive drawing rendering
support from NSImage, NSString, etc.) and not some model object that
has drawing code tacked onto it. These view classes should be told or
be able to ask for the data they need to use at render time, ideally
pre-prepared / pre-fetched as makes sense.

Here I'd disagree. My drawing objects are based neither on NSView nor NSCell, they start from NSObject and gradually specialise, first with properties that are common to all "drawables" and then subclassing for more esoteric variations. NSView is too heavyweight, especially when you could end up with tens of thousands of objects, and NSCell, while lightweight, is also very much designed for use in framework tables, matrixes and controls. Objects that are part of a drawing are not really "control-like" so the NSCell model isn't really that good a fit. It also means a lot of code isn't directly available to the application developer so often behaviours have to be inferred, whereas by writing your own "drawable" object you have complete control over its code and behaviour.

In my case the "drawables" are just as lightweight as NSCell - probably more so as they don't need to be compatible with existing controls, tables or matrixes.


I don't get this at all (not just the quoted comments, but going all the way back to Graham's original statement).

-- If a failure in the drawing code destroys the *data model* (thereby preventing it from being saved) then there's something terribly wrong with the drawing code's design. (Surely, being *drawing* code it only needs read-only access to the data model. Or, if it has drawing-related status to update in the data model, it shouldn't be making structural alterations to the data model whose failure could leave the model in an un-savable state.)


Well, it's not really in an unsaveable state. The data model itself isn't damaged by exceptions thrown from the drawing code. However, the visual appearance of the document is badly upset (scrollbars suddenly disappear, rulers get drawn in the middle of the view, etc). The user *thinks* something has gone terribly wrong, panics, and typically closes the document without saving. In fact saving at that point will work OK, but we're finding that users typically think it's become "corrupt" and are loth to save over an earlier version of the document because they think that what they see is what they will get - i.e. saving the document with a corrupted view will make the problem permanent. The end result is the same though - the user has "lost" their data, even though in fact they didn't need to. This is why I need to ensure that a minor glitch when drawing doesn't get blown out of all proportion.

IMO, the issue is (at least initially) a matter of documentation. The NSGraphicsContext class reference is ambiguous and incomplete -- it's not clear whether the current context is saved *in* each state on the state stack, or whether the state stack is *in* the current context, or whether the state *is* the context. There's apparently a setGraphicsState: method which *looks* like it might be what Graham needs to set things back to a known state, but the parameter to this method isn't documented anywhere I could find.

The header file is no help at all. The comments refer to save/ restore *context* methods (as opposed to save/restore *state* methods) which apparently don't exist anywhere.

If the documentation was clear, I think Graham's strategy for dealing with exceptions would also become clear.




Yep, I guess this is getting at the heart of my problem (Thanks for putting it so much more succinctly than I have managed so far!). I guess I'm hoping someone familiar with NSGraphicsContext can help me understand if saving and restoring a context explictly is legal (what happens to all the saved states that were stacked up in the process?) or if not, what is an appropriate alternative. In practice it seems to work, but I need to know that I'm not storing up trouble for myself.

How about wrapping save/restore in a macro, and expanding them to use
@try/@finally? For example:

#define SAVE_GSTATE @try { [NSGraphicsContext saveGraphicsState];
#define RESTORE_GSTATE } @finally { [NSGraphicsContext restoreGraphicsState]; }



This a great, simple idea. I will look into doing this.

Thanks to everyone who chipped in, a very useful discussion.

cheers, Graham





_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to arch...@mail-archive.com

Reply via email to