On 19/06/2011, at 10:46 PM, Matthias Arndt wrote:

> In a document-based app my custom view draws some thousand paths in drawRect: 
> with a good performance. Now I'd like to offer a "slow-motion" animation, so 
> the user can actually watch the paths being drawn (not each single one, but 
> e. g. in steps of 100 paths per sec).
> 
> I though of several approaches and all of them seem to be infeasible:
> 
> 1. Sleeping the drawing loop in drawRect: (or make the runLoop wait for some 
> time) and use [... flushGraphics]: Freezes the GUI, as the app is 
> single-threaded
> 2. Moving the drawing in a 2nd thread and then pause this one: AFAIK is 
> drawing in a second thread not allowed in Cocoa
> 3. Limit the drawing loop to an increasing high bound, and setup a timer to 
> fire [self setNeedsDisplay:YES] periodically: Causes the first x paths being 
> redrawn at each animation step, resulting in a bad performance
> 4. Same approach, but skipping the first x paths in the next animation step: 
> Corrupted display, e. g. while resizing in an animation
> 
> I'm racking my brains over this, any suggestions?


Ken's thoughts regarding Core Animation are good ones. I've started doing some 
stuff with it only recently and have found it really good for this sort of 
thing. But I also found you need to plan your design with it in mind - it's not 
so easy to retro-fit to an existing design.

Some general comments:

1. Don't do drawing in a thread.
2. Don't put any unnecessary delays in -drawRect:
3. Don't even think about using threads for this.

Your (4) is looking like the best approach - use a timer to schedule a periodic 
update of the view, and during that update, draw only some portion of the 
content on top of what you've drawn already. That leaves the issue of view 
resizing, which you'll need to take into account. You can get notified when the 
view is resizing, and modify your update schedule to ensure that it does what 
you want - typically, it will need to flag that the background needs to be 
erased and that the objects need to be redrawn from the beginning.

So let's say you have a total number of objects needing to be drawn. You can 
also select that a smaller range of them be drawn each time. As Ken suggested, 
using NSRange is a good way to track this. So, this should give you a rough 
idea (typed into mail, untested):

@interface DelayView : NSView
{
    NSRange             _animationRange;        // range of objects to be drawn 
at each animation frame
    BOOL                        _needsErase;            // set to YES to erase 
the view's background, must be inited to YES
}

@end

#define  OBJECTS_TO_BE_DRAWN_PER_FRAME          10      // number of objects 
drawn each frame

@implementation DelayView


- (void)                drawRect:(NSRect) updateRect
{
    if( _needsErase )
    {
        [[self backgroundColor] set];
        NSRectFill( updateRect );
        _needsErase = NO;
    }

    NSUInteger n;
    
    for( n = _animationRange.location; n < NSMaxRange( _animationRange ); ++n )
        [self drawObjectAtIndex:n];
}


- (void)                timerCallback:(NSTimer*) timer
{
    _animationRange.location += OBJECTS_TO_BE_DRAWN_PER_FRAME;
    
    if( NSMaxRange( _animationRange ) > [self countOfObjects])
   {
        // warning: be careful about signed and unsigned arithmetic here
        NSInteger len =  [self countOfObjectsToBeDrawn] - 
_animationRange.location;

        if( len > 0 )
             _animationRange.length = len;
        else
             _animationRange.length = 0;

        // after this subset has been drawn, all objects will have been drawn 
and there's no more to do for now
    }

    if( _animationRange.length > 0 )
        [self setNeedsDisplay:YES];
}


- (void)                setFrame:(NSRect) frame
{
        [super setFrame:frame];

        _animationRange.location = 0;
        _animationRange.length = OBJECTS_TO_BE_DRAWN_PER_FRAME; // or maybe all 
objects?
        _needsErase = YES;

        [self setNeedsDisplay:YES];
}



To redraw the objects from the beginning, you need to do what -setFrame: does - 
reset the range of objects to be animated and flag the erase, which clears the 
background. Then, the timer (scheduled elsewhere, not shown here), calls the 
timer callback which schedules a new chunk of objects to be drawn and so on, 
but without erasing. When all objects have been drawn, no further animation is 
performed. By setting the _animationRange.length to the total number of 
objects, and .location to 0, you can draw everything in one go without 
animation, so the same mechanism can be used for both the animated and 
non-animated case.

--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