Animation with UIViews - Objective-C - objective-c

Currently, I figured out how to animate an image that is in my UIViewController:
UIImage *image = [UIImage imageNamed:#"Logo.png"];
CALayer *logoLayer = [CALayer layer];
logoLayer.bounds = CGRectMake(0, 0, image.size.width, image.size.height);
logoLayer.position = CGPointMake(300, 216);
logoLayer.contents = (id)image.CGImage;
CAKeyframeAnimation *moveAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
moveAnimation.path = path.CGPath;
moveAnimation.duration = 2.0f;
[logoLayer addAnimation:moveAnimation forKey:#"moveAnimation"];
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.duration = 2.0f;
animationGroup.autoreverses = NO;
animationGroup.repeatCount = 1; //HUGE_VALF
[animationGroup setAnimations:[NSArray arrayWithObjects: moveAnimation, nil]];
[logoLayer addAnimation:animationGroup forKey:#"animationGroup"];
But this is based on a logoLayer, which is an image. How could I animate something like this but for a UIView? Such as a UIButton?

In general, rather than using CAAnimations on CALayers, you can use UIView animation. There are ways to set animation curve, delay, repeat, reversal, and so on using the block-based animation API or the old delegate-based API.
As for your specific case of the CAKeyframeAnimation, you can try the answer to this question.

Related

How to use an additive animation on AVPlayerLayer bounds?

I'm trying to do an additive animation on a AVPlayerLayer's bounds. By simply setting additive to YES, it breaks the animation. I need to do it additive so that the width and height can be animated with different animation parameters. Animating the key paths bounds.size.width and bounds.size.height do not work either.
I can animate other CALayer classes fine with the below code, so I'm starting to wonder if AVPlayerLayer has a bug.
Here is the code:
AVPlayerLayer *vl;
// ...
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
NSURL *path = [[NSBundle mainBundle] URLForResource:#"vid" withExtension:#"mp4"]];
AVPlayer *player = [AVPlayer playerWithURL: path];
vl = [AVPlayerLayer playerLayerWithPlayer:player];
vl.videoGravity = AVLayerVideoGravityResize;
// vl = [CALayer layer]; // a normal calayer animates properly
vl.backgroundColor = [NSColor redColor].CGColor;
vl.frame = CGRectMake(200,100,150,100);
[_window.contentView.layer addSublayer:vl];
}
- (IBAction)animate:(id)sender {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"bounds"];
animation.fromValue = [NSValue valueWithRect:CGRectMake(0,0,0, 0)];
animation.toValue = [NSValue valueWithRect:CGRectMake(0,0,240, 60)];
animation.duration = 4;
animation.additive = YES;
[vl addAnimation:animation forKey:nil];
}
This is what it looks like:
The red area should be the video playing.
You can check out the sample project I created here:
https://www.dropbox.com/s/jxuh69wiqdwnuc6/PlayerLayer%20size%20Animation.zip?dl=1
The frame property of a CALayer is a derived property, dependent on the position, anchorPoint, bounds and transform of the layer. Instead of animating the frame, you should instead animate the position or bounds, depending on what effect you are trying to accomplish.
You may first take a look at this:
Q: When I try to animate the frame of a CALayer nothing happens. Why?
This can also help:
How to animate the frame of an layer with CABasicAnimation?
And those code may be helpul to you:
NSURL *fileUrl = [NSURL fileURLWithPath:#"yourFilePath"];
AVPlayer *player = [[AVPlayer alloc] initWithURL:fileUrl];
CALayer *superlayer = self.view.layer;
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
[superlayer addSublayer:playerLayer];
[player play];
playerLayer.videoGravity = AVLayerVideoGravityResize;
playerLayer.frame = CGRectMake(10, 200, 320, 200);
playerLayer.position = CGPointMake(self.view.bounds.size.width/2, self.view.bounds.size.height/2);
CGRect startBounds = CGRectMake(10, 200, 200, 150);
CGRect endBounds = CGRectMake(10, 200, 320, 200);
CABasicAnimation * sizeAnimation = [CABasicAnimation animationWithKeyPath:#"bounds"];
sizeAnimation.fromValue = [NSValue valueWithRect:startBounds] ;
sizeAnimation.toValue = [NSValue valueWithRect:endBounds] ;
sizeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
sizeAnimation.duration = 4;
[playerLayer addAnimation:sizeAnimation forKey:#"bounds"];

Adding animated layer on video using CAKeyframeAnimation

I'm trying to add an overlay to my video which contains a sublayer that has the content changed over time. So I'm adding CAKeyframeAnimation to the layer as follows:
CALayer *overlayLayer = [CALayer layer];
[overlayLayer setFrame:CGRectMake(0, 0, size.width, size.height)];
[overlayLayer setMasksToBounds:YES];
NSArray *frames = [self.gifImage frames];
CALayer *aLayer = [CALayer layer];
UIImage *firstImage = [frames objectAtIndex:0];
CGFloat nativeWidth = CGImageGetWidth(firstImage.CGImage);
CGFloat nativeHeight = CGImageGetHeight(firstImage.CGImage);
CGRect frame = CGRectMake(0.0, 0.0, nativeWidth, nativeHeight);
aLayer.frame = frame;
aLayer.contents = (id)firstImage.CGImage;
[aLayer setMasksToBounds:YES];
CAKeyframeAnimation *animationSequence = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
animationSequence.calculationMode = kCAAnimationDiscrete;
animationSequence.duration = [self.gifImage totalDuration];
animationSequence.repeatCount = HUGE_VALF;
NSMutableArray *animationSequenceArray = [[NSMutableArray alloc] init];
for (UIImage *image in frames) {
[animationSequenceArray addObject:(id)image.CGImage];
}
animationSequence.values = animationSequenceArray;
[aLayer addAnimation:animationSequence forKey:#"contents"];
[overlayLayer addSublayer:aLayer];
CALayer *parentLayer = [CALayer layer];
CALayer *videoLayer = [CALayer layer];
parentLayer.frame = CGRectMake(0, 0, size.width, size.height);
videoLayer.frame = CGRectMake(0, 0, size.width, size.height);
[parentLayer addSublayer:videoLayer];
[parentLayer addSublayer:overlayLayer];
composition.animationTool = [AVVideoCompositionCoreAnimationTool
videoCompositionCoreAnimationToolWithPostProcessingAsVideoLayer:videoLayer inLayer:parentLayer];
Somehow when I add aLayer as a sublayer of my view, it animates properly. But When I add it to the overlay of the video, the animation doesn't work. Is there something I'm missing here? Any help is appreciated.
If you use kCAAnimationDiscrete, here's a checklist:
keyTimes needs a value from 0-1, not a time
keyTimes must start at 0.0 and end with value 1.0
keyTimes must have one more value than your animation frames count
I've finally managed to fix this issue by changing calculationMode to kCAAnimationLinear. This seems to be a silly issue, but I hope it may be useful for someone.

Animate Mask in Objective C

I am making an application where I have to show 3 images in a single screen, and when User touch one image then that Image should be shown by Animating and resizing.
So for 1st Step I did Masking of 3 images with 3 different Transparent Mask Image from
How to Mask an UIImageView
And my method is like below
- (UIImageView*) maskedImage:(UIImage *)image withMasked:(UIImage *)maskImage {
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
CALayer *mask1 = [CALayer layer];
mask1.contents = (id)[maskImage CGImage];
mask1.frame = CGRectMake(0, 0, 1024, 768);
imageView.layer.mask = mask1;
imageView.layer.masksToBounds = YES;
return imageView;
}
And I am calling this as
self.image2 = [UIImage imageNamed:#"screen2Full.png"];
self.mask2 = [UIImage imageNamed:#"maskImage.png"];
self.imageView2 = [self maskedImage:self.image2 withMasked:self.mask2];
[self.completeSingleView addSubview:self.imageView2];
This is working perfectly.
Now for Step 2. Animate the Mask, so that Full Image can be shown. I have googled a lot about this but didn't get success. So Please help me. I just want to know how can we Animate and Resize the Mask Image So that I can view a full Image. My concept is as below.
So finally i got the solution from multiple sites and some own logic and hard work obviously ;). So here is the solution of this
-(void)moveLayer:(CALayer*)layer to:(CGPoint)point
{
// Prepare the animation from the current position to the new position
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"position"];
animation.fromValue = [layer valueForKey:#"position"];
animation.toValue = [NSValue valueWithCGPoint:point];
animation.duration = .3;
layer.position = point;
[layer addAnimation:animation forKey:#"position"];
}
-(void)resizeLayer:(CALayer*)layer to:(CGSize)size
{
CGRect oldBounds = layer.bounds;
CGRect newBounds = oldBounds;
newBounds.size = size;
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"bounds"];
animation.fromValue = [NSValue valueWithCGRect:oldBounds];
animation.toValue = [NSValue valueWithCGRect:newBounds];
animation.duration = .3;
layer.bounds = newBounds;
[layer addAnimation:animation forKey:#"bounds"];
}
- (UIImageView*) animateMaskImage:(UIImage *)image withMask:(UIImage *)maskImage width:(int )wd andHeight:(int )ht andSize:(CGSize)size andPosition:(CGPoint)pt {
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
CALayer *mask = [CALayer layer];
mask.contents = (id)[maskImage CGImage];
mask.frame = CGRectMake(0, 0, wd, ht);
//This needs to be place before masking. I don't knwo why. But its working :)
[self resizeLayer:mask to:size];
[self moveLayer:mask to:pt];
imageView.layer.mask = mask;
imageView.layer.masksToBounds = YES;
return imageView;
}
You just need to call this as
[self.imageView1 removeFromSuperview];
self.imageView1 = [self animateMaskedImage:self.image1 withMasked:self.mask1 width:1024 andHeight:768 andSize:CGSizeMake(1024*4, 768*4) andPosition:CGPointMake(1024*2, 768*2)];
[self.completeSingleView addSubview:self.imageView1];

Using a CALayer as a mask to a backing layer of a GLKView blocks touch events?

I'm trying to use a CAShapeLayer as a mask to the backing layer of a GLKView like so:
CGRect circleFrame = CGRectMake(0, 0, 800, 800);
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:circleFrame];
CGPoint circleAnchor = CGPointMake(CGRectGetMidX(circleFrame) / CGRectGetMaxX(circleFrame),CGRectGetMidY(circleFrame) / CGRectGetMaxY(circleFrame));
shapeLayerMask = [[CAShapeLayer alloc] init];
shapeLayerMask.path = path.CGPath;
shapeLayerMask.anchorPoint = circleAnchor;
shapeLayerMask.frame = CGRectMake(0, 0,800, 800);
shapeLayerMask.fillColor = [UIColor blackColor].CGColor;
shapeLayerMask.backgroundColor = [UIColor clearColor].CGColor;
shapeLayerMask.opacity = 1.0f;
shapeLayerMask.hidden = NO;
self.view.layer.mask = shapeLayerMask;
But as soon as i do this, all my touch events stop firing. No more touchesBegan, touchesEnded, etc. Anyone knows why? Thanks in advance!

Animation of stroke of circle segment ends in complete stroke

I'm trying to animate the appearance of a segment of a circle. To archive this I use a CABasicAnimations which works quiet fine.
The animation starts on top and moves quiet nicely to one third of the whole circle. But when the animation finishes, the circle is being drawn completely immediately.
How can I prevent that?
Here is the source code of my custom UIView:
- (void)drawRect:(CGRect)rect
{
int radius = 100;
int strokeWidth = 10;
CGColorRef color = [UIColor redColor].CGColor;
int timeInSeconds = 5;
CGFloat startAngle = 0;
CGFloat endAngle = 0.33;
CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius) cornerRadius:radius].CGPath;
circle.position = CGPointMake(CGRectGetMidX(self.frame)-radius, CGRectGetMidY(self.frame)-radius);
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = color;
circle.lineWidth = strokeWidth;
[self.layer addSublayer:circle];
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = timeInSeconds;
drawAnimation.repeatCount = 1.0;
drawAnimation.removedOnCompletion = NO;
drawAnimation.fromValue = [NSNumber numberWithFloat:startAngle];
drawAnimation.toValue = [NSNumber numberWithFloat:endAngle];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[circle addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
}
When you apply an animation to a layer, Core Animation creates a copy of the layer and animates the properties of the copy. The original layer is called the model layer and the copy is called the presentation layer. An animation never changes the properties of the model layer.
You tried to fix this by setting removedOnCompletion to NO. You would also have to set the fillMode of the animation to make this work, but it's not really the correct way to animate a property.
The correct way is to change the property on your model layer, then apply the animation.
// Change the model layer's property first.
circle.strokeEnd = endAngle;
// Then apply the animation.
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = timeInSeconds;
drawAnimation.fromValue = [NSNumber numberWithFloat:startAngle];
drawAnimation.toValue = [NSNumber numberWithFloat:endAngle];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[circle addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
This is explained in the Core Animation Essentials video from WWDC 2011. I highly recommend watching it.