According to the core animation documentation, setting the layer contents should trigger an implicit 0.25 animation that will transition between the new and old images.
I also see many places where people are asking how to remove this implicit animation but for some reason when I try this on my project, I get an instant swap of images.
Reading some more into the documention I saw this code snipped:
- (id<CAAction>)actionForLayer:(CALayer *)theLayer
forKey:(NSString *)theKey {
CATransition *theAnimation=nil;
if ([theKey isEqualToString:#"contents"]) {
theAnimation = [[CATransition alloc] init];
theAnimation.duration = 1.0;
theAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
theAnimation.type = kCATransitionPush;
theAnimation.subtype = kCATransitionFromRight;
}
return theAnimation;
}
Which implies that "contents" is not implicitly animated.
I would be very interested to understand this better.
OK, I'm copying some code from an app of mine. First I usually get CATransition not with alloc..init.. but using +animation. Second I'm not seeing that you are adding the transition to a layer. Third I'm not sure that CATransition are implicitly animated, CALayer properties yes.
CATransition *transition = [CATransition animation];
transition.duration = 1;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionPush;
transition.subtype = slideCATransitionCompareTable[ arc4random() % 4]; //to get a random subtype
[self.slideShowReceiptImageView.layer addAnimation:transition forKey:nil];
Related
A code snippet from https://github.com/danielamitay/DACircularProgress/blob/master/DACircularProgress/DACircularProgressView.m
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"progress"];
animation.duration = duration;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fillMode = kCAFillModeForwards;
animation.fromValue = [NSNumber numberWithFloat:self.progress];
animation.toValue = [NSNumber numberWithFloat:pinnedProgress];
animation.beginTime = CACurrentMediaTime() + initialDelay;
animation.delegate = self;
[self.circularProgressLayer addAnimation:animation forKey:#"progress"];
But I could not find progress in the offical document. Does the keypath need to be animatable property? What does the progress mean?
From this answer (which is coincidentally animating progress too):
Firstly we need to create a new subclass of CALayer that has an animatable property called 'progress'.
Your snippet does indeed have a property called 'progress'
#property(nonatomic) CGFloat progress;
It appears the animation is animating that property
It is because the progress is an animatable property for this specific uiview. We could custom animatable property refer: Create a custom animatable property
I have added transition animation to UILabel for text change.
But it takes place only once when I add it to layer.
- (IBAction)changeText:(id)sender {
if (!self.transitionAnimation) {
CATransition *animation = [CATransition animation];
animation.duration = 0.5;
animation.type = kCATransitionFade;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.removedOnCompletion = NO;
[self.label.layer addAnimation:animation forKey:nil];
self.transitionAnimation = animation;
}
self.label.text = [NSString stringWithFormat:#"Text %d",arc4random()];
}
Why do you need to have a strong reference to the animation? That reference doesn't get nil'ed out, and hence your control doesn't enter the if block more than once.
Either nil out the transitionAnimation property on didStop, or don't store a reference at all.
Edit: Also, I don't think you need to have removedOnCompletion as NO.
I am currently sliding in an animated image from the right of the screen using CATransition:
CATransition *transition = [CATransition animation];
transition.duration = TRANSITION_DURATION;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
[self.layer addAnimation:transition forKey:nil];
self.image = [UIImage animatedImageNamed:prefix duration:ANIMATION_DURATION];
My question is, how do I slide the image out? Once the image animation cycles through, I want to send it off the screen in the direction from whence it came, i.e. TransitionToRight, but there doesn't seem to be such a transition type. Please suggest how I might accomplish this reverse transition.
Here is how I might write it, if such a reverse transition existed:
[self performSelector:#selector(removeAnimatedImage) withObject:nil afterDelay:ANIMATION_DURATION];
and inside removeAnimatedImage, I would create another CATransition animation with this reverse direction to send it off the screen.
Did you try the kCATransitionFromLeft version of the push transition? It should be the inverse of FromRight.
Not the ideal solution, but one hack is to either animate a change in frame bounds, or applying a translation transform animation:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:TRANSITION_DURATION];
if (enteredFromRight) {
self.transform = CGAffineTransformMakeTranslation(SCREEN_SPACE_WIDTH, 0);
} else {
self.transform = CGAffineTransformMakeTranslation(-SCREEN_SPACE_WIDTH, 0);
}
[UIView commitAnimations];
I have the following situation:
From within a view controller, I go about creating an arbitrary number of transparent UIButtons which toogle the appearance and disappearance of UIViews over the controller's main view. Right now, when my "zoom in" and "zoom out" methods are called, there's code for modifying the default animation of two CALayer properties (just duration really). This code then gets called every time the actions take place. But I'd like to set the animation code once inside the layer and not have it in the zoom methods. I'll explain more after I show the basic idea:
The buttons/sensors creation code is too long to post (and not that relevant to the real question), but it's something along the lines of this pseudo code:
- (void) createSensors:[number of sensors]
{
for(number of sensors)
{
create UIVIew with some content;
create transparent UIBUtton as sensor and place somewhere in main view;
calculate a transform to "shrink" the UIView's layer to the size and position of the button.
apply transform;
set the UIVIew's layer opacity to 0;
add UIVIew to main view
(but the rest of my interface is in a container view, so I can dim the screen and have this view displayed in a modal-like way.)
do some tagging so the button's tag can later be used to retrieve this view and transform.
}
}
Now, when a sensor is clicked, I perform a "zoom in" for the view associated with the sensor/view. I actually dim the screen to white, and animate the view's (the layer's really) position and scaling to the center of the screen. And on clicking a button in the view, I do the opposite to dismiss it. This is the code:
- (void)animateZoomIn:(UIButton*)sender
{
NSString *sourceTagKey = [NSString stringWithFormat:#"%i",sender.tag];
AnimationSettings *settings = [zoomTransforms objectForKey:sourceTagKey];
UIView *newBox = [[self.view subviews] objectAtIndex:sender.tag];
activeZoomBox = newBox;
CABasicAnimation *a = [CABasicAnimation animation];
a.duration = .4;
[mainCanvas.layer addAnimation:a forKey:#"opacity"];
a = [CABasicAnimation animation];
a.duration = 1;
[activeZoomBox.layer addAnimation:a forKey:#"opacity"];
a = [CABasicAnimation animation];
a.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
a.duration = .5;
[activeZoomBox.layer addAnimation:a forKey:#"transform"];
[mainCanvas setUserInteractionEnabled:NO];
if([settings wantsBackgroundFade])
[mainCanvas.layer setOpacity:.2];
[activeZoomBox.layer setOpacity:1];
[activeZoomBox.layer setTransform:CATransform3DIdentity];
}
- (void)animateZoomOut:(UIButton*)sender
{
NSString *sourceTagKey = [NSString stringWithFormat:#"%i",sender.tag];
AnimationSettings *settings = [zoomTransforms objectForKey:sourceTagKey];
CABasicAnimation *a = [CABasicAnimation animation];
a.duration = .4;
[mainCanvas.layer addAnimation:a forKey:#"opacity"];
a = [CABasicAnimation animation];
a.duration = 1;
[activeZoomBox.layer addAnimation:a forKey:#"opacity"];
a = [CABasicAnimation animation];
a.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
a.duration = .5;
[activeZoomBox.layer addAnimation:a forKey:#"transform"];
[mainCanvas setUserInteractionEnabled:YES];
if([settings wantsBackgroundFade])
[mainCanvas.layer setOpacity:1];
[activeZoomBox.layer setOpacity:0];
[activeZoomBox.layer setTransform:[settings transform]];
}
So, the question:
I'd like to not have this kind of code:
CABasicAnimation *a = [CABasicAnimation animation];
a.duration = .4;
[mainCanvas.layer addAnimation:a forKey:#"opacity"];
in the action methods. I'd rather replace the animation once and make it "permanent" for each layer created and for the mainCanvas view. For instance, I'd prefer to have this code once on the controller's init method and forget about it:
CABasicAnimation *a = [CABasicAnimation animation];
a.duration = .4;
NSMutableDictionary *animations = [NSMutableDictionary dictionaryWithDictionary:[mainCanvas.layer actions]];
[animations setObject:a forKey:#"opacity"];
[mainCanvas.layer setActions:animations];
Then, whenever I'd call [mainCanvas setOpacity:] it would last .4 secs by default. With a delegate-less CALayer it'd work.But in this case, my layers have their views as delegates, which have precedence over the actions dictionary, and I sort of need the views as delegates..
So is there a way to still have the layers respond to the dictionary instead? And if not, what would be the best way to "permanently replace" or add animations to these layers, so that the animations are contained in the layer objects (which is why I'd prefer to not override the view's delegate methods for the CAAction Protocol)?
Why not just override -actionForLayer:forKey: on the UIView to provide the action you want on the important keys (and call super for the others)? There is no way to change the precedence, so your only options are implement -actionForLayer:forKey: or go with the manual animations.
I have a UINavigationController whose UIView slides in from the bottom of the screen when the user presses a button.
Immediately after I set the view's "hidden" property to NO, though, the UINavigationController's view sometimes appears fully in place for one frame, as if the animation was finished already.
This is the code that shows/hides the view:
- (void)showGUI: (bool)show
{
CATransition *transition = [CATransition animation];
transition.duration = 0.5;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
if (!show)
{
transition.type = kCATransitionReveal;
transition.subtype = kCATransitionFromBottom;
}
else
{
transition.type = kCATransitionMoveIn;
transition.subtype = kCATransitionFromTop;
}
[navController.view.superview.layer addAnimation:transition forKey:nil];
navController.view.hidden = !show;
}
CATransition animations are applied when layers are added/removed from the layer you add the animation to. If toggling the hidden property doesn't work, then try removing the view instead.