Question

In my UIViewController I create CALayer with image1.

CALayer *imageLayer = [CALayer layer];
[imageLayer setBounds:CGRectMake(0.0, 0.0, 241.0, 430.0)];
[imageLayer setPosition:CGPointMake(160.0, 60.0)];
[imageLayer setAnchorPoint:CGPointMake(0.5, 0.0)];
[imageLayer setContents:(id)[UIImage imageNamed:@"image1.png".CGImage];
[imageLayer setContentsGravity:kCAGravityResizeAspect];
[self.view.layer addSublayer:imageLayer];

Later in UIViewController I want to change image by pushing it using CATransition.

CATransition *t = [CATransition animation];
t.beginTime = [self.view.layer convertTime:CACurrentMediaTime() + 6.5 fromLayer:nil];
t.type = kCATransitionPush;
t.subtype = kCATransitionFromRight;
[imageLayer setContents:(id)[UIImage imageNamed:@"image2.png"].CGImage];    
[imageLayer addAnimation:t forKey:nil];

When running the code I get image2 from the beginning, transition works though by pushing itself after 6.5 seconds. Where's the image1? What's wrong?

Correct answer: Thanks both Jacky Boy and Tiago Almeida. I corrected the code removing the line

t.beginTime = [self.view.layer convertTime:CACurrentMediaTime() + 6.5 fromLayer:nil];

and adding 'dispatch_after'.

Now it works:

CATransition *t = [CATransition animation];
t.type = kCATransitionPush;
t.subtype = kCATransitionFromRight;
[imageLayer setContents:(id)[UIImage imageNamed:@"image2.png"].CGImage];    
[imageLayer addAnimation:t forKey:nil];
[CATransaction begin];
NSTimeInterval delayInSeconds = 6.5;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [imageLayer setContents:(id)[UIImage imageNamed:@"image2.png"].CGImage];
    [imageLayer addAnimation:t forKey:nil];
});
[CATransaction commit];

Thanks again, guys!

Was it helpful?

Solution

Remove the following line:

t.beginTime = [self.view.layer convertTime:CACurrentMediaTime() + 1.5 fromLayer:nil];

This is what delaying the animation, so the layer is setting the new content, and only after the layer is animated. If you want to specify the duration of the animation:

[CATransaction begin];
[CATransaction setAnimationDuration:1.0];

CATransition *t = [CATransition animation];
t.type = kCATransitionPush;
t.subtype = kCATransitionFromRight;
[_layer setContents:(id)[UIImage imageNamed:@"image2.jpg"].CGImage];
[_layer addAnimation:t forKey:nil];


[CATransaction commit];

Or a smaller version:

CATransition *t = [CATransition animation];
t.type = kCATransitionPush;
[t setDuration:2.5];
t.subtype = kCATransitionFromRight;
[_layer setContents:(id)[UIImage imageNamed:@"image2.jpg"].CGImage];
[_layer addAnimation:t forKey:nil];

I would prefer this:

CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.duration = .25f;
transition.subtype = kCATransitionFromRight;

_layer.actions = @{@"contents": transition};

And when you want to change the content:

[_layer setContents:(id)[UIImage imageNamed:@"image2.jpg"].CGImage];

OTHER TIPS

You are creating the imageLayer but configuring the currentCharacterImageLayer.

The correct solution here is actually to use the fillMode property on the transition. I.e. (in Swift sorry)

transition.fillMode = .backwards

From the docs

If you delay the start of an animation, you might also want to set the fillMode property to kCAFillModeBackwards. This fill mode causes the layer to display the animation’s start value, even if the layer object in the layer tree contains a different value. Without this fill mode, you would see a jump to the final value before the animation starts executing. Other fill modes are available too.

dispatch_after will work for some situations, but will not work if you need to compose animations, e.g. using CATransaction

Thanks for posting the question, it put me on the right track to finding the answer

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