Drawing animation
Asked Answered
T

1

6

I'm creating a simple app where when the user presses a button, a series of lines will be drawn on the screen and the user will be able to see these lines drawn in real time (almost like an animation).

My code looks something like this (has been simplified):

UIGraphicsBeginImageContext(CGSizeMake(300,300));
CGContextRef context = UIGraphicsGetCurrentContext();

for (int i = 0; i < 100; i++ ) {
    CGContextMoveToPoint(context, i, i);
    CGContextAddLineToPoint(context, i+20, i+20);
    CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
    CGContextStrokePath(context);
}

UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

My problem is that:

1) As soon as the user presses the button, the UIThread blocks until the drawing is done.

2) I can't get the lines to be drawn on the screen one at a time - I've tried setting the UIImage directly inside the loop and also tried setting a layer content inside the loop.

How do I get around these problems?

Thekla answered 9/10, 2012 at 20:41 Comment(3)
Does this help ? #9246454Singapore
what kind of delay do you want?Jabin
A customisable delay would be good - I think Rob had what I was looking for... thanks for your help!Thekla
H
16

You say "just like an animation". Why not do an actual animation, a la Core Graphics' CABasicAnimation? Do you really need to show it as a series of lines, or is a proper animation ok?

If you want to animate the actual drawing of the line, you could do something like:

#import <QuartzCore/QuartzCore.h>

- (void)drawBezierAnimate:(BOOL)animate
{
    UIBezierPath *bezierPath = [self bezierPath];

    CAShapeLayer *bezier = [[CAShapeLayer alloc] init];

    bezier.path          = bezierPath.CGPath;
    bezier.strokeColor   = [UIColor blueColor].CGColor;
    bezier.fillColor     = [UIColor clearColor].CGColor;
    bezier.lineWidth     = 5.0;
    bezier.strokeStart   = 0.0;
    bezier.strokeEnd     = 1.0;
    [self.view.layer addSublayer:bezier];

    if (animate)
    {
        CABasicAnimation *animateStrokeEnd = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
        animateStrokeEnd.duration  = 10.0;
        animateStrokeEnd.fromValue = [NSNumber numberWithFloat:0.0f];
        animateStrokeEnd.toValue   = [NSNumber numberWithFloat:1.0f];
        [bezier addAnimation:animateStrokeEnd forKey:@"strokeEndAnimation"];
    }
}

Then all you have to do is create the UIBezierPath for your line, e.g.:

- (UIBezierPath *)bezierPath
{
    UIBezierPath *path = [UIBezierPath bezierPath];

    [path moveToPoint:CGPointMake(0.0, 0.0)];

    [path addLineToPoint:CGPointMake(200.0, 200.0)];

    return path;
}

If you want, you can patch a bunch of lines together into a single path, e.g. here is a roughly sine curve shaped series of lines:

- (UIBezierPath *)bezierPath
{
    UIBezierPath *path = [UIBezierPath bezierPath];
    CGPoint point = self.view.center;

    [path moveToPoint:CGPointMake(0, self.view.frame.size.height / 2.0)];

    for (CGFloat f = 0.0; f < M_PI * 2; f += 0.75)
    {
        point = CGPointMake(f / (M_PI * 2) * self.view.frame.size.width, sinf(f) * 200.0 + self.view.frame.size.height / 2.0);
        [path addLineToPoint:point];
    }

    return path;
}

And these don't block the main thread.

By the way, you'll obviously have to add the CoreGraphics.framework to your target's Build Settings under Link Binary With Libraries.

Hild answered 10/10, 2012 at 3:33 Comment(4)
That's excellent - thank you! Just another question - I'm calling [path addLineToPoint] many times (maybe 1000+). After I implemented your method, I find that my application and the device its running on becomes extremely slow/jerky. I profiled the application and doesn't seem to be using a huge amount of CPU - do you know why this would be?Thekla
@Thekla I could easily imagine that this would tax the device. Once you start exceeding, say, 30 line segments per second, the harder it will be for the app to keep up with the animation. I don't know where the cut-off is (is it 30/second? 60? 100? I don't know), but at some point, the amount of time that it takes to draw the paths for a given frame of the animation, the longer each frame will take. And once the animation slows to much less than 30 frames per second, the more jerky it will be. You have to somehow cut down on the number of line segments that need to be drawn per second. Sigh.Hild
@Thekla Perhaps you can set the duration of the animation to be x / y where x is the number of line segments and y is the number of segments per second you'll support (a figure that you'll probably want to experiment with, to figure out what you can get away with without any jerkiness, maybe 30 or 60 or 100 or ...).Hild
That makes sense - the performance definitely improves when I cut the number of lines down. Thanks a lot for your help!Thekla

© 2022 - 2024 — McMap. All rights reserved.