1

I am posting this question in response to one of the answers on my previous question here: Multiple CALayer masks causing performance issues

So, now that I am attempting to go down the pre-rendered animation approach, I am still unable to get a smooth animation. Not only that, but when run on an actual device, the app crashes periodically due to memory issues.

You can see the animation running here: http://cl.ly/e3Qu (It may not look so bad from the video, but focus on the edge of animation, and it performs worse on an actual device.)

Here is my code:

static CGFloat const animationDuration = 1.5;
static CGFloat const calculationRate = (1.0/40.0); // 40fps max.
static CGFloat const calculationCycles = animationDuration/calculationRate;


@implementation splashView {

    CADisplayLink* l;

    CGImageRef backgroundImg;

    UIColor* color;

    NSMutableArray* animationImages;

    NSTimeInterval currentTime;
}

-(void) beginAnimating {
    static dispatch_once_t d;
    dispatch_once(&d, ^{

        CGFloat totalDistance = 0;
        CGFloat screenProgress = 0;
        CGFloat deltaScreenProgress = 0;


        totalDistance = screenHeight()+screenWidth();

        color = [[lzyColors colors] randomColor];

        backgroundImg = textBG(color, screenSize()).CGImage;

        animationImages = [NSMutableArray array];

        NSLog(@"start");

        UIGraphicsBeginImageContextWithOptions(screenSize(), YES, 0);

        CGContextRef c = UIGraphicsGetCurrentContext();

        for (int i = 0; i <= (calculationCycles+1); i++) {

            UIImage* img = lzyCGImageFromDrawing(^{

                CGFloat height = screenHeight();
                CGFloat width = screenWidth();

                CGMutablePathRef p = CGPathCreateMutable();

                CGPoint startingPoint = [self pointBForProgress:screenProgress];

                CGPathMoveToPoint(p, nil, startingPoint.x, startingPoint.y);
                lzyCGPathAddLineToPath(p, [self pointAForProgress:screenProgress]);
                if ((width < screenProgress) && (screenProgress-deltaScreenProgress) < width) {
                    lzyCGPathAddLineToPath(p, (CGPoint){width, 0});
                }
                if (deltaScreenProgress != 0) lzyCGPathAddLineToPath(p, [self pointAForProgress:screenProgress-deltaScreenProgress-1]);
                if (deltaScreenProgress != 0) lzyCGPathAddLineToPath(p, [self pointBForProgress:screenProgress-deltaScreenProgress-1]);
                if ((height < screenProgress) && (screenProgress-deltaScreenProgress) < height) {
                    lzyCGPathAddLineToPath(p, (CGPoint){0, height});
                }
                CGPathCloseSubpath(p);

                CGContextAddPath(c, p);
                CGContextClip(c);
                CGPathRelease(p);

                CGContextSetFillColorWithColor(c, color.CGColor);
                CGContextFillRect(c, self.bounds);


                CGContextDrawImage(c, self.bounds, backgroundImg);

            });


            [animationImages addObject:img];

            deltaScreenProgress = screenProgress;
            screenProgress = (i*totalDistance)/calculationCycles;
            deltaScreenProgress = screenProgress-deltaScreenProgress;
        }


        NSLog(@"stop");


        currentTime = 0;

        l = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkDidFire)];
        [l addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

    });


}

-(void) displayLinkDidFire {

    NSTimeInterval deltaTime = l.duration;
    currentTime += deltaTime;

    if (currentTime <= animationDuration) {

        CGFloat prg = (currentTime/animationDuration);
        NSInteger image = roundf(([animationImages count]-1)*prg);

        [CATransaction begin];
        [CATransaction setDisableActions:YES];
        self.layer.contents = (__bridge id _Nullable)(((UIImage*)[animationImages objectAtIndex:image]).CGImage);
        [CATransaction commit];
    } else {

        [CATransaction begin];
        [CATransaction setDisableActions:YES];
        self.layer.contents = (__bridge id _Nullable)(((UIImage*)[animationImages lastObject]).CGImage);
        [CATransaction commit];
        [l invalidate];
        animationImages = nil;
    }

}


-(CGPoint) pointAForProgress:(CGFloat)progressVar {
    CGFloat width = screenWidth();
    return (CGPoint){(progressVar<width)?progressVar:width+1, (progressVar>width)?progressVar-width:-1};
}

-(CGPoint) pointBForProgress:(CGFloat)progressVar {
    CGFloat height = screenHeight();
    return (CGPoint){(progressVar>height)?(progressVar-height):-1, (progressVar<height)?progressVar:height+1};
}

@end

The textBG() function just does some fairly simple Core Graphics drawing to get the background image.

I can only assume I'm doing something fundamentally wrong here, but I can't think of what it is.

Any suggestions on how to improve performance and reduce memory consumption (without degrading the quality of the animation)?

Community
  • 1
  • 1
Hamish
  • 78,605
  • 19
  • 187
  • 280

2 Answers2

1

Animating a full-screen image via layer contents is definitely going to have performance and memory problems, especially on @3x devices. For the animation you’re showing in the other question (this video), it doesn’t look like you actually need any masking at all—create a series of rectangular solid-colored layers (black, light purple, medium purple, dark purple), layer them from front to back (with the text layer in between the light and medium ones), rotate them to the angle you need, and move them around as needed.

If you end up needing a more complicated animation that that approach won’t work for—or in general, for animating arbitrary full-screen content—you’ll need to either (1) pre-render it as a video (either offline or using the AVFoundation APIs) and play it back that way or (2) use OpenGL or Metal to do the drawing.

Noah Witherspoon
  • 57,021
  • 16
  • 130
  • 131
  • well the problem is, it may be difficult to tell from the video, but each layer has a gradient to it. Therefore moving the layers themselves would have a unwanted effect. I think I'm just going to make the leap into openGL. – Hamish Dec 14 '15 at 13:43
0

The problem you have there is some poorly written animation logic, it is basically allocating a whole bunch of images in memory in a way that is sure to crash your device sooner of later. You need to start over with a better approach that does not pull all image data into memory at the same time. Don't just try to tweak what you already have because the basic assumptions made by the previous developer are just plain wrong. See my previous example to a like question for some good links like the "video-and-memory-usage-on-ios-devices" one: https://stackoverflow.com/a/17224710/763355

Community
  • 1
  • 1
MoDJ
  • 4,309
  • 2
  • 30
  • 65
  • Note that a very simplified impl (code is a bit old now, but basic approach still works) is also available: http://stackoverflow.com/a/4650537/763355 – MoDJ Dec 11 '15 at 21:41
  • I'm still not happy with the delay that this would cause upon app opening, even if I were to save the animation to disk after, it would have to be done at least 15 times to account for the different colors I plan on using. Thanks for your help, but like I said in the comment on Noah's answer, I think I'm just going to make the leap into openGL which should help provide me with a more general solution that'll fare better with more complex animations. – Hamish Dec 14 '15 at 13:47
  • Well, your implementation is completely up to you. Just keep in mind that you may not have considered all the possibilities and that starting with something that already works can save you weeks of effort. For example, you might want to have a look at this blog post: http://www.modejong.com/blog/post11_opengl_color_cycle/index.html – MoDJ Dec 14 '15 at 21:52