Question

I've been having a problem that I can't seem to figure out. In my app I use a custom UIImageView class to represent movable objects. Some are loaded with static .png images and use frame animation, while others are loaded with CAShapeLayer paths from .svg files. For some of those, I'm getting stutter when the layer is animating from one path to another, while the UIImageView is moving.

You can see it in the linked video. When I touch the mermaid horn, a note spawns (svg path) and animates into a fish (another svg path), all while drifting upwards. The stuttering occurs during that animation / drift. It's most noticeable the third time I spawn the fish, around 19 seconds into the video. (The jumping at the end of each animation I need to fix separately so don't worry about that)

http://www.youtube.com/watch?v=lnrNWuvqQ4w

This does not occur when I test the app in iOS Simulator - everything is smooth. On my iPad 2 it stutters. When I turn off the note/fish movement (drifting upward), it animates smooth on the iPad, so obviously it has to do with moving the view at the same time. There's something I'm missing but I can't figure it out.

Here is how I set up the animation. PocketSVG is a class I found on Github that converts an .svg to a Bezier path.

animShape = [CAShapeLayer layer];
[animShape setShouldRasterize:YES];
[animShape setRasterizationScale:[[UIScreen mainScreen] scale]];

PocketSVG *tSVG;
UIBezierPath *tBezier;

PocketSVG *tSVG2;
UIBezierPath *tBezier2;
CAShapeLayer *tLayer2;
CABasicAnimation *animBezier;
CABasicAnimation *animScale;

tSVG = [[PocketSVG alloc] initFromSVGFileNamed:[NSString stringWithFormat:@"%@", tName]];

// Use PocketSVG to convert the SVG to a Bezier Path
tBezier = tSVG.bezier;
animShape.path = tBezier.CGPath;
animShape.lineWidth = 1;
animShape.strokeColor = [[UIColor blackColor] CGColor];
animShape.fillColor = [[UIColor blackColor] CGColor];
animShape.fillRule = kCAFillRuleNonZero;

// Set the frame & bounds to position the piece and set anchor point for rotation
// parentPaper is the Mermaid the note spawns from
CGPoint newCenter;
CGFloat imageScale = fabsf(parentPaper.transform.a);
newCenter = // Code excised, set center based on custom spawn points in relation to parent position and scale

animShape.bounds = CGPathGetBoundingBox(animShape.path);

CGRect lBounds = CGRectMake(newCenter.x, newCenter.y, animShape.bounds.size.width, animShape.bounds.size.height);
[animShape setFrame:lBounds];

halfSize = CGPointMake(animShape.bounds.size.width/2, animShape.bounds.size.height/2);
animShape.anchorPoint = CGPointMake(prp.childSpawnX, prp.childSpawnY);

// Create the end path for animation
tSVG2 = [[PocketSVG alloc] initFromSVGFileNamed:[NSString stringWithFormat:@"%@2", tName]];
tBezier2 = tSVG2.bezier;
tLayer2 = [CAShapeLayer layer];
tLayer2.path = tBezier2.CGPath;

// Create the animation and add it to the layer
animBezier = [CABasicAnimation animationWithKeyPath:@"path"];
animBezier.delegate = self;
animBezier.duration = prp.frameDur;
animBezier.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animBezier.fillMode = kCAFillModeForwards;
animBezier.removedOnCompletion = NO;
animBezier.autoreverses = behavior.autoReverse;

if (prp.animID < 0)  {
    animBezier.repeatCount = FLT_MAX;
}
else {
    animBezier.repeatCount = prp.animID;
}

animBezier.fromValue = (id)animShape.path;
animBezier.toValue = (id)tLayer2.path;
[animShape addAnimation:animBezier forKey:@"animatePath"];

// also scale the animation if spawned from a parent
//   i.e. small note to normal-sized fish
if (parentPaper != nil) {

    animScale = [CABasicAnimation animationWithKeyPath:@"transform"];
    animScale.delegate = self;
    animScale.duration = prp.frameDur;
    animScale.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    animScale.fillMode = kCAFillModeForwards;
    animScale.removedOnCompletion = YES;
    animScale.autoreverses = NO;
    animScale.repeatCount = 1;
    animScale.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(imageScale, imageScale, 0.0)];
    animScale.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.0, 1.0, 0.0)];
    [animShape addAnimation:animScale forKey:@"animateScale"];
}

[self.layer addSublayer:animShape];

And this is basically how I'm moving the UIImageView. I use a CADisplayLink at 60 frames, figure out the new spot based on existing position of animShape.position, and update like this, where self is the UIImageView:

[self.animShape setPosition:paperCenter];

Everything else runs smooth, it's just this one set of objects that stutter and jump about when running on the iPad itself. Any ideas of what I'm doing wrong? Moving it the wrong way, perhaps? I do get confused with layers, frames and bounds still.

Was it helpful?

Solution

To fix this I ended up changing the way the UIImageView moves. Instead of changing the frame position through my game loop, I switched it to a CAKeyframeAnimation on the position since it's a temporary movement that only runs once. I used CGPathAddCurveToPoint to replicate the sine wave movement that I originally had. It was rather simple and I probably should have done it that way to begin with.

I can only surmise that the stuttering was due to moving it through the CADisplayLink loop while its animation separately.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top