How to use transition in CAKeyframeAnimation - objective-c

I want to show a few images using CAKeyframeAnimation, how can I perform transitions between images? Or how can I add CATransition to the animation? Such as: image1 eases out then image2 eases in, image2 fades out then image3 fades in, etc

CAKeyframeAnimation :
- (void)animateImages
{
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
keyframeAnimation.values = self.imagesArray;
keyframeAnimation.repeatCount = 1.0f;
keyframeAnimation.duration = kAnimationDuration; // static const with your value
keyframeAnimation.delegate = self;
keyframeAnimation.removedOnCompletion = NO;
keyframeAnimation.fillMode = kCAFillModeForwards;
CALayer *layer = self.animationImageView.layer;
[layer addAnimation:keyframeAnimation
forKey:#"girlAnimation"];
}

One layer for one image works good. I can add animation at the begin/end of every layer to show in/out transition.

Related

How to animate a CALayer attached to UIImageView?

I am using this code proposed by Bartosz to add a mask to an UIImageView. It works fine.
#import <QuartzCore/QuartzCore.h>
CALayer *mask = [CALayer layer];
mask.contents = (id)[[UIImage imageNamed:#"mask.png"] CGImage];
mask.frame = CGRectMake(0, 0, 320.0, 100.0);
yourImageView.layer.mask = mask;
yourImageView.layer.masksToBounds = YES;
In addition, I want to animate the mask, e.g. sliding the mask to the right, so that at the end of the animation, the mask is not applied to the UIImageView any more.
In my specific case, the mask uses a fully transparent image, so the UIImageView is not visible at the initial state (which works fine), but is expected to be so at the end of the animation. However, the idea may be reused to any other use case were masks need to be animated.
The idea is to manipulate the x-origin portion of the frame of the mask. So, I came up with this code:
[UIView animateWithDuration: 0.2
delay: 0
options: UIViewAnimationCurveEaseInOut
animations:^{
CGRect maskFrame = yourImageView.layer.mask.frame;
maskFrame.origin.x = 320.0;
yourImageView.layer.mask.frame = maskFrame;
}
completion:^(BOOL finished){}];
Unfortunately, the mask is applied to the whole UIImageView at any time, it's not sliding to the right.
UPDATE 1:
This is the code I am actually using the set up the view and mask: It's a UITableViewCell.
APPCell.m (APPCell.h "extends" UITableViewCell)
#import "APPCell.h"
#import <QuartzCore/QuartzCore.h>
#interface APPCell()
#property (strong, nonatomic) UIImageView *menu;
#property (strong, nonatomic) CALayer *menuMask;
...
#end
#implementation APPCell
...
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self.menu = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 320.0, 88.0)];
[self.menu setBackgroundColor:[UIColor clearColor]];
[self.menu setImage:[UIImage imageNamed:#"cell_back"]];
[self addSubview:self.menu];
self.menuMask = [CALayer layer];
self.menuMask.contents = (id)[[UIImage imageNamed:#"cell_mask"] CGImage];
self.menuMask.frame = CGRectMake(0, 0, 320.0, 88.0);
self.menu.layer.mask = self.menuMask;
self.menu.layer.masksToBounds = YES;
}
...
Instead of animating with the help of UIKit, I am now using implicit animation of CoreAnimation to move the mask layer:
APPCell.m
...
- (void)swipeLeft
{
self.menuMask.position = CGPointMake(-320.0, 0.0);
}
...
I can confirm that swipeLeft is called. I expect the mask "to be gone" and to see the [UIImage imageNamed:#"cell_back"]], which I do when I uncomment self.menu.layer.mask = self.menuMask.
Solution:
Instead of setting the content on the CALayer, I set the background color to white. This is the code I am using:
self.menuSubMenuMask = [CALayer layer];
self.menuSubMenuMask.backgroundColor = [[UIColor whiteColor] CGColor];
self.menuSubMenuMask.frame = CGRectMake(320.0, 0.0, 320.0, 88.0);
self.tableCellSubMenu.layer.mask = self.menuSubMenuMask;
self.tableCellSubMenu.layer.masksToBounds = YES;
In order to show the UIImageView the CALayer is applied to, the CALayer must NOT be "above" the UIImageView.
Animation with UIKit of UIViews is much more limited than using Core Animation directly. In particular what you are trying to animate is not one of animatable properties of a UIView. In addition as clarified in the View Programming Guide for iOS:
Note: If your view hosts custom layer objects—that is, layer objects without an associated view—you must use Core Animation to animate any changes to them.
This is the case in your example. You have added a CALayer to your view and UIKit will not be able to animate the result for you. On the other hand you can use Core Animation directly to animate the motion of your mask layer. You should be able to do this easily using implicit animation as described in the Core Animation Programming Guide. Please note that from the list of CALayer Animatable Properties that frame is not animatable. Instead you should use position.
You can achieve something you want by using CATransition, although this might not be the solution you want:
1) At first, set mask for your layer just as you did
2) When you want to remove mask and reveal your image, use the following code:
CATransition* transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.subtype = kCATransitionFromRight;
transition.duration = 1.0;
[mask addAnimation:transition forKey:kCATransition];
imageView.layer.mask.contents = [UIImage imageNamed:#"black.png"].CGImage;
The main trick here - we created transition animation for our mask layer, so this animation will be applied when you change any (i'm not sure about any) property of mask layer. Now we set mask's content to completely black image to remove masking at all - now we've got smooth pushing animation where our masked image is going to the left and unmasked image is getting into its place
The easiest way is to use CoreAnimation itself:
CGPoint fromPoint = mask.position;
CGPoint toPoint = CGPointMake(fromPoint.x*3.0, fromPoint.y);
mask.position = toPoint; // CoreAnimation animations do *not* persist
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.fromValue = [NSValue valueWithCGPoint:fromPoint];
animation.toValue = [NSValue valueWithCGPoint:toPoint];
animation.duration = 4.0f;
[mask addAnimation:animation forKey:#"position"];

Lag with CALayer when double tap on the home button

When I put shadow etc. with CALayer my app is lagging when I double tap on the home button to see tasks running. I don't have any other lag, just when I double tap.
I call this method 20 times to put 20 images :
- (UIView *)createImage:(CGFloat)posX posY:(CGFloat)posY imgName:(NSString *)imgName
{
UIView *myView = [[UIView alloc] init];
CALayer *sublayer = [CALayer layer];
sublayer.backgroundColor = [UIColor blueColor].CGColor;
sublayer.shadowOffset = CGSizeMake(0, 3);
sublayer.shadowRadius = 5.0;
sublayer.shadowColor = [UIColor blackColor].CGColor;
sublayer.shadowOpacity = 0.8;
sublayer.frame = CGRectMake(posX, posY, 65, 65);
sublayer.borderColor = [UIColor blackColor].CGColor;
sublayer.borderWidth = 2.0;
sublayer.cornerRadius = 10.0;
CALayer *imageLayer = [CALayer layer];
imageLayer.frame = sublayer.bounds;
imageLayer.cornerRadius = 10.0;
imageLayer.contents = (id) [UIImage imageNamed:imgName].CGImage;
imageLayer.masksToBounds = YES;
[sublayer addSublayer:imageLayer];
[myView.layer addSublayer:sublayer];
return myView;
}
I have commented all my code except this, so I'm sure the lag comes from here. Also I've checked with the Allocations tools and my app never exceeded 1Mo. When I'm just putting images without shadow etc. everything works fine.
Try setting a shadowPath on the layer as well. It will need to be a rounded rect since you've got rounded corners on your layer.
CALayer has to calculate where it is drawing, and where to put the shadow, if it doesn't have a shadow path. This has a big effect on animation performance.
Another way to improve performance with CALayers is to set the shouldRasterize property to YES. This stores the layer contents as a bitmap and prevents it having to re-render everything.

image rotation, covers the button

I want to rotate the image around the x-axis from left to right. The problem is that when you rotate the image covers the button located on the top
Run animation
[AnimationUtil rotationRightToLeftForView:image andDuration:1];
Animation metod
+(void) rotationRightToLeftForView:(UIView *)flipView andDuration:(NSTimeInterval)duration{
// Remove existing animations before stating new animation
[flipView.layer removeAllAnimations];
// Make sure view is visible
flipView.hidden = NO;
// show 1/2 animation
//flipView.layer.doubleSided = NO;
// disable the view so it’s not doing anythign while animating
flipView.userInteractionEnabled = NO;
// Set the CALayer anchorPoint to the left edge and
// translate the button to account for the new
// anchorPoint. In case you want to reuse the animation
// for this button, we only do the translation and
// anchor point setting once.
if (flipView.layer.anchorPoint.x != 0.0f) {
flipView.layer.anchorPoint = CGPointMake(0.0f, 0.5f);
flipView.center = CGPointMake(flipView.center.x-flipView.bounds.size.width/2.0f, flipView.center.y);
}
// create an animation to hold the page turning
CABasicAnimation *transformAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
transformAnimation.removedOnCompletion = NO;
transformAnimation.duration = duration;
transformAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
// start the animation from the current state
transformAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
// this is the basic rotation by 180 degree along the y-axis M_PI
CATransform3D endTransform = CATransform3DMakeRotation(radians(180.0), 0.0f, -1.0f, 0.0f);
transformAnimation.toValue = [NSValue valueWithCATransform3D:endTransform];
// Create an animation group to hold the rotation
CAAnimationGroup *theGroup = [CAAnimationGroup animation];
// Set self as the delegate to receive notification when the animation finishes
theGroup.delegate = self;
theGroup.duration = duration;
// CAAnimation-objects support arbitrary Key-Value pairs, we add the UIView tag
// to identify the animation later when it finishes
[theGroup setValue:[NSNumber numberWithInt:flipView.tag] forKey:#"viewFlipTag"];
// Here you could add other animations to the array
theGroup.animations = [NSArray arrayWithObjects:transformAnimation, nil];
theGroup.removedOnCompletion = NO;
// Add the animation group to the layer
[flipView.layer addAnimation:theGroup forKey:#"flipView"];
}
The decision follows:
Create mask (image) Color = black, make region Size = Button.size Color = transparent
image name = "mask2.png"
button = ...;
ImageView = ...;
parent_view = ...;
UIImage *_maskingImage = [UIImage imageNamed:#"mask2"];
CALayer *_maskingLayer = [CALayer layer];
_maskingLayer.frame = parent_View.bounds;
[_maskingLayer setContents:(id)[_maskingImage CGImage]];
CALayer *layerForImage = [[CALayer alloc] init];
layerForImage.frame = parent_View.bounds;
[layerForImage setMask:_maskingLayer];
[layerForImage addSublayer:ImageView.layer];
[parent_View.layer addSublayer:layerForImage];
[parent_View addSubView:button];

Animating a gaussian blur using core animation?

I'm trying to animate something where it's initially blurry then it comes into focus. I guess it works OK, but when the animation is done it's still a little blurry. Am I doing this wrong?
CABasicAnimation* blurAnimation = [CABasicAnimation animation];
CIFilter *blurFilter = [CIFilter filterWithName:#"CIGaussianBlur"];
[blurFilter setDefaults];
[blurFilter setValue:[NSNumber numberWithFloat:0.0] forKey:#"inputRadius"];
[blurFilter setName:#"blur"];
[[self layer] setFilters:[NSArray arrayWithObject:blurFilter]];
blurAnimation.keyPath = #"filters.blur.inputRadius";
blurAnimation.fromValue = [NSNumber numberWithFloat:10.0f];
blurAnimation.toValue = [NSNumber numberWithFloat:1.0];
blurAnimation.duration = 1.2;
[self.layer addAnimation:blurAnimation forKey:#"blurAnimation"];
Your problem is that the animation stops and is automatically removed, but the filter lingers with the tiniest of blur applied.
What you want to do is to remove the blur filter when the animation completes. You need to add a delegate to the CABasicAnimation instance and implement the -[id<CAAnimationDelegate> animationDidStop:finished:] method.
If you let self be the delegate in this case it should be fairly simple, add this line before adding the animation to your layer:
blurAnimation.delegate = self;
And the callback is equally simple:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
[[self layer] setFilters:nil];
}
If you're looking for an optimized way to animate a blur then I recommend creating a single blurred image of your view and then fading the blurred image from alpha 0 to 1 over the top of your original view. Seems nice and fast in tests.

How to add an animated layer at a specific index

I am adding two CAText layers to a view and animating one of them. I want to animate one layer above the other but it doesn't get positioned correctly in the layer hierarchy until the animation has finished. Can anyone see what I have done wrong? The animation works, it is just running behind 'topcharlayer2' until the animation has finished.
- (CABasicAnimation *)topCharFlap
{
CABasicAnimation *flipAnimation;
flipAnimation = [CABasicAnimation animationWithKeyPath:#"transform"];
flipAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(1.57f, 1, 0, 0)];
flipAnimation.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(0.0, 1, 0, 0)];
flipAnimation.autoreverses = NO;
flipAnimation.duration = 0.5f;
flipAnimation.repeatCount = 10;
return flipAnimation;
}
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
[self setBackgroundColor:[UIColor clearColor]]; //makes this view transparent other than what is drawn.
[self initChar];
}
return self;
}
static CATransform3D CATransform3DMakePerspective(CGFloat z)
{
CATransform3D t = CATransform3DIdentity;
t.m34 = - 1. / z;
return t;
}
-(void) initChar
{
UIFont *theFont = [UIFont fontWithName:#"AmericanTypewriter" size:FONT_SIZE];
self.layer.sublayerTransform = CATransform3DMakePerspective(-1000.0f);
topHalfCharLayer2 = [CATextLayer layer];
topHalfCharLayer2.bounds = CGRectMake(0.0f, 0.0f, CHARACTERS_WIDTH, 100.0f);
topHalfCharLayer2.string = #"R";
topHalfCharLayer2.font = theFont.fontName;
topHalfCharLayer2.fontSize = FONT_SIZE;
topHalfCharLayer2.backgroundColor = [UIColor blackColor].CGColor;
topHalfCharLayer2.position = CGPointMake(CGRectGetMidX(self.bounds),CGRectGetMidY(self.bounds));
topHalfCharLayer2.wrapped = NO;
topHalfCharLayer1 = [CATextLayer layer];
topHalfCharLayer1.bounds = CGRectMake(0.0f, 0.0f, CHARACTERS_WIDTH, 100.0f);
topHalfCharLayer1.string = #"T";
topHalfCharLayer1.font = theFont.fontName;
topHalfCharLayer1.fontSize = FONT_SIZE;
topHalfCharLayer1.backgroundColor = [UIColor redColor].CGColor;
topHalfCharLayer1.position = CGPointMake(CGRectGetMidX(self.bounds),CGRectGetMidY(self.bounds));
topHalfCharLayer1.wrapped = NO;
//topHalfCharLayer1.zPosition = 100;
[topHalfCharLayer1 setAnchorPoint:CGPointMake(0.5f,1.0f)];
[[self layer] addSublayer:topHalfCharLayer1 ];
[[self layer] insertSublayer:topHalfCharLayer2 atIndex:0];
[topHalfCharLayer1 addAnimation:[self topCharFlap] forKey:#"anythingILikeApparently"];
}
The View which contains this code is loaded by a view controller in loadView. The initChar method is called in the view's initWithFrame method. The target is iOS4. I'm not using setWantsLayer as I've read that UIView in iOS is automatically layer backed and doesn't require this.
A couple thoughts come to mind:
Try adding the 'R' layer to the layer hierarchy before you start the animation.
Instead of inserting the 'T' layer at index 1, use [[self layer] addSublayer: topHalfCharLayer1]; to add it and then do the insert for the 'R' layer with [[self layer] insertSublayer:topHalfCharLayer2 atIndex:0];
Have you tried to play with the layer zPosition? This determines the visual appearance of the layers. It doesn't actually shift the layer order, but will change the way they display--e.g. which layers is in front of/behind which.
I would also suggest you remove the animation code until you get the layer view order sorted. Once you've done that, the animation should just work.
If you have further issues, let me know in the comments.
Best regards.
From the quartz-dev apple mailing list:
Generally in a 2D case, addSublayer will draw the new layer above the
previous. However, I believe this implementation mechanism is
independent of zPosition and probably just uses something like
painter's algorithm. But the moment you add zPositions and 3D, I don't
think you can solely rely on layer ordering. But I am actually unclear
if Apple guarantees anything in the case where you have not set
zPositions on your layers but have a 3D transform matrix set.
So, it seems I have to set the zPosition explicitly when applying 3D transforms to layers.
/* Insert 'layer' at position 'idx' in the receiver's sublayers array.
* If 'layer' already has a superlayer, it will be removed before being
* inserted. */
open func insertSublayer(_ layer: CALayer, at idx: UInt32)