AVFoundation focus animation using Core Animation - ios

So,I want to show a square box, a I typical focus animation when a user taps on the screen. Here is what I have tried:
-(void)showFocusAnimation:(CGPoint)location{
UIView *square = [[UIView alloc]initWithFrame:CGRectMake(location.x, location.y, 40, 40)];
square.alpha=1.0;
square.layer.borderColor = (__bridge CGColorRef)[UIColor colorWithRed:12.0/255.0 green:185.0/255.0 blue:249.0/255.0 alpha:1];
square.layer.borderWidth = 2.0;
[overlayView addSubview:square];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.1];
square.frame = CGRectMake(location.x, location.y, 90, 90);
[UIView commitAnimations];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.1];
square.frame = CGRectMake(location.x, location.y, 40, 40);
[UIView commitAnimations];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:.2];
square.frame = CGRectMake(location.x, location.y, 90, 90);
square.alpha = 0;
[UIView commitAnimations];
}
I have a couple of problems that I cant seem to solve :
I can't get my border to show up.
Currently I am drawing a square starting from the point where the user tapped the screen. The point at which the user taps it, should actually be the center of the square.
I can't seem to get the animation correct. What I am trying to do is, decrease the square size, increase it and then decrease it again and then the alpha = 0.
I thought if I have 3 different separate animations maybe it will work, is not working.

Your problem is that triggering animations is asynchronous so they all start at the same time and first two frame animations are replaced by the third.
One thing that you could do instead is to use Core Animation (your question actually uses UIView animation and the not even the new block stuff) to create an animation group for the size and opacity animations. It would look something like this (note I didn't run this so it may contain typos and such)
CAKeyframeAnimation *resize = [CAKeyframeAnimation animationWithKeyPath:#"bounds.size"];
resize.values = #[[NSValue valueWithCGSize:CGSizeMake(40, 40)],
[NSValue valueWithCGSize:CGSizeMake(90, 90)],
[NSValue valueWithCGSize:CGSizeMake(40, 40)],
[NSValue valueWithCGSize:CGSizeMake(90, 90)]];
resize.keyTimes = #[#0, #.25, #.5, #1];
CABasicAnimation *fadeOut = [CABasicAnimation animationWithKeyPath:#"opacity"];
fadeOut.toValue = #0;
fadeOut.beginTime = .2;
CAAnimationGroup *both = [CAAnimationGroup animation];
both.animations = #[resize, fadeOut];
CALayer *squareLayer = nil; // Your layer code here.
[squareLayer addAnimation:both forKey:#"Do the focus animation"];
// Make sure to remove the layer after the animation completes.
Things to note are:
I'm animating bounds.size because the frame isn't really changing and it's better to be precise.
The group has the total duration
keyTimes are specified from 0 to 1
When the animation completes it will be removed from the layer.
Since the last thing in your animation is to fade the opacity to 0 you should removed it when you are done. One way of doing this is to become the delegate of the group and implement
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
// remove layer here since it should have faded to 0 opacity
}

Related

Animation subview removal freezes when scrolling

My code:
I am using the following code for an add to basket animation:
-(void)triggerAddItemAnimation:(NSIndexPath*)indexPath
{
//Cell that was pressed.
CellMenuItem *cell = [self.tableView cellForRowAtIndexPath:indexPath];
// grab the imageview using cell
UIImageView *imgV = (UIImageView*)[cell viewWithTag:IDENTIFIER_ANIMATION_IMAGE];
//Get the x-coordinate of the image.
int xCoord = cell.imgAddButton.frame.origin.x;
// get the exact location of image
CGRect rect = [imgV.superview convertRect:imgV.frame fromView:nil];
rect = CGRectMake(xCoord, (rect.origin.y*-1)-10, imgV.frame.size.width, imgV.frame.size.height);
//NSLog(#"rect is %f,%f,%f,%f",rect.origin.x,rect.origin.y,rect.size.width,rect.size.height);
// create new duplicate image
UIImageView *starView = [[UIImageView alloc] initWithImage:imgV.image];
[starView setFrame:rect];
starView.layer.cornerRadius=5;
starView.layer.borderColor=[[UIColor blackColor]CGColor];
starView.layer.borderWidth=1;
[self.view addSubview:starView];
// begin ---- apply position animation
CAKeyframeAnimation *pathAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
pathAnimation.calculationMode = kCAAnimationPaced;
pathAnimation.fillMode = kCAFillModeForwards;
pathAnimation.removedOnCompletion = NO;
pathAnimation.duration=0.65;
pathAnimation.delegate=self;
// tab-bar right side item frame-point = end point
CGPoint endPoint = CGPointMake(self.lblBasketItemsCount.frame.origin.x, self.lblBasketItemsCount.frame.origin.y);
CGMutablePathRef curvedPath = CGPathCreateMutable();
CGPathMoveToPoint(curvedPath, NULL, starView.frame.origin.x, starView.frame.origin.y);
CGPathAddCurveToPoint(curvedPath, NULL, endPoint.x, starView.frame.origin.y, endPoint.x, starView.frame.origin.y, endPoint.x, endPoint.y);
pathAnimation.path = curvedPath;
CGPathRelease(curvedPath);
// end ---- apply position animation
float animationDuration = 0.65f;
// apply transform animation
CABasicAnimation *basic=[CABasicAnimation animationWithKeyPath:#"transform"];
[basic setToValue:[NSValue valueWithCATransform3D:CATransform3DMakeScale(0.25, 0.25, 0.25)]];
[basic setAutoreverses:NO];
[basic setDuration:animationDuration];
[starView.layer addAnimation:pathAnimation forKey:#"curveAnimation"];
[starView.layer addAnimation:basic forKey:#"transform"];
//Remove the animation 0.05 before the animationDuration to remove clipping issue.
[starView performSelector:#selector(removeFromSuperview) withObject:nil afterDelay:animationDuration - 0.05f];
}
The Problem:
The animation works beautifully but when I scroll the image used for the animation (taken from the cell) does not remove from the view until the scrolling has stopped.
The subview being removed is removed with the last line of code within the method:
[starView performSelector:#selector(removeFromSuperview) withObject:nil afterDelay:animationDuration - 0.05f];
What I've tried:
Ive tried using GCD:
dispatch_async(dispatch_get_main_queue(), ^{
//call method here....
});
If I recall I believe Ive also tried a run loop with a timer, as well as perform selector on main thread.
Ive also tried putting the method in a block(not the actual code I used, merely an example):
[UIView animateWithDuration:0.5
delay:1.0
options: UIViewAnimationCurveEaseOut
animations:^{
}
completion:^(BOOL finished){
//Remove subview here
}];
The Question:
I can deduce that the scrolling is tying up the main thread, hence why I've tried to call the method asynchronously.
Can someone let me know what I'm doing wrong ?
To solve this issue you can use the complete portion of a block as can be seen above. I was simply using the block incorrectly at the time.
However the way I solved this, because I couldn't get a path animation to work in a block was to set the delegate of the animation to self.
And use the following:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
[theView removeFromSuperview];
}

How to reduce the opacity of an UIButton gradually?

In my project i have an UIButton which on clicking zooms. Everything is fine till here but i require the opacity of the button to gradually decrease from 1 to 0. I mean it should decrease in 1,0.9,0.8,0.7,0.6,0.5,0.4,0.3,0.2,0.1,0.0 sequence. My current code is
-(void)roundButtonAnimation
{
self.buttonZoom = [CAKeyframeAnimation animationWithKeyPath:#"transform"];
self.buttonZoom.duration = 0.8f;
self.buttonZoom.values = #[[NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(2,2,2)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(3,3,3)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(5,5,5)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(6,6,6)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(7,7,7)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(9,9,9)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(10,10,10)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(11,11,11)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(12,12,12)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(13,13,13)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(14,14,14)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(15,15,15)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(16,16,16)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(17,17,17)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(18,18,18)],[NSValue valueWithCATransform3D:CATransform3DMakeScale(50,50,50)]];
self.buttonZoom.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
self.roundButton.transform = CGAffineTransformMakeScale(0,0);
self.roundButton.alpha = 0.3;
[self.roundButton.layer addAnimation:self.buttonZoom forKey:#"buttonScale"];
[self.CLSpinnerView1 stopAnimating];
//[self performSelector:#selector(secondView) withObject:self afterDelay:0.3];
[self performSelector:#selector(postNotification) withObject:self afterDelay:0.6];
}
[UIView animateWithDuration:1.0
delay:0.0
options: UIViewAnimationCurveLinear
animations:^{
self.roundButton.transform = CGAffineTransformMakeScale(50.0f, 50.0f);
self.roundButton.alpha = 0.0;
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
}
Let's explain a little, in this animation block you say that the animation should take place in one second, during this second based on a linear curve (thus always equal increment/decrement) the button should zoom 50 times and alpha should be animate to 0.
I do not understand why you are using a more complicated CAKeyframeAnimation over that simple animation block.
Keyframe animation are useful when you want more power over timings and animate over a set of values.
UIView animateWithDuration:animations: should serve your purpose here.
[UIView animateWithDuration:1.0 animations:^{
button.alpha = 0.0;
}];
Will gradually change the button's alpha to 0 over one second (...WithDuration:1.0...).
See the The UIView Class Reference for more information.
Hope this helps.
Try this
[button.layer removeAnimationForKey:#"opacity"];

Card flipping animation

I am making a card game and would like to use a Card flipping animation.
Currently I'm using this code with two images, front and back:
card, is a uiview with two UIImageView propertys
[UIView transitionWithView:card duration:0.65f
options:UIViewAnimationOptionTransitionFlipFromRight animations:^{
card.backImageView.image = card.frontImageView.image;
card.layer.shadowOpacity = 0.8;
} completion:^(BOOL finished){
card.layer.shadowOpacity = 0.0;
}];
In order to create a basic card flipping animation like the one in the video you've linked to, I suggest putting frontImageView and the backImageView directly on top of each other on the UIView you intend to flip. To start, set their images to front and back accordingly; and, in this particular case, hide the frontImageView and show the backImageView.
Assuming "card" is the UIView you intend to flip, to perform the flip try:
[UIView transitionWithView:card duration:0.65f
options:UIViewAnimationOptionTransitionFlipFromRight animations:^{
frontImageView.hidden = NO;
backImageView.hidden = YES;
} completion:^(BOOL finished) {
// whatever you'd like to do immediately after the flip completes
}];
Edit:
And to handle the shadow, first off, it appears in the video you posted that the shadow grows in length moreso than it just fades in. And it seems as if (and makes logical sense that) the shadow reaches its peak during the middle of the animation as the card is lifted at its highest point. Since the shadow grows then shrinks during the course of the flip animation, it doesn't make sense to include the shadow animation within the same animation block as the flip since they're on different time schedules.
Secondly with regard to the shadow, to animate the layer property, you have to use Core Animations.
Perhaps you can run the two animations concurrently, i.e. while the above animation is performing, also do something like:
CABasicAnimation *shadowAnimation = [CABasicAnimation animationWithKeyPath:#"shadowRadius"];
shadowAnimation.delegate = self;
[shadowAnimation setFromValue:[NSNumber numberWithFloat:3.0]];
[shadowAnimation setToValue:[NSNumber numberWithFloat:10.0]];
[shadowAnimation setDuration:0.65f];
shadowAnimation.autoreverses = YES;
[[card layer] addAnimation:shadowAnimation forKey:#"shadowRadius"];
The last portion has been adapted from this code and takes advantage of the autoreverse property to automatically reverse the shadow's growth.
This is how to perform a card flipping animation in swift 3:
UIView.transition(from: frontImageView,
to: backImageView,
duration: 0.65,
options: .transitionFlipFromRight,
completion: nil)
i tested a few things, and Compromise with this:
_tempTransform = card.transform;
[card loadFront];//loads the front image.
card.frontImageView.hidden=YES;
[UIView animateWithDuration:0.02f
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / -500;
transform = CATransform3DRotate(transform, 15.0f * M_PI / 180.0f, 0, -1, 0.0f);
card.layer.transform = transform;
}completion:^(BOOL done){
card.layer.shadowOpacity=0.1f;
card.transform=CGAffineTransformScale(card.transform, 1.2f, 1.2f);
[UIView transitionWithView:card duration:0.4f
options:UIViewAnimationOptionTransitionFlipFromRight animations:^{
card.frontImageView.hidden=NO;
card.backImageView.hidden=YES;
} completion:^(BOOL finished){
card.layer.shadowOpacity=0.0f;
card.transform=_tempTransform;
}];
}];

Moving Animation of a CALayer?

I have a array of CALayers that i'm looping and trying to move.
Tile is a CALayer subclass and has a CGRect property named originalFrame where i store the frame i want to animate to.
When i'm useing the code below everything is instant moved to the correct possition and there is no 4 sec animation. How can i animate these CALayer?
for (int i = 0; i < [tileArray count]; i++) {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDelay:i];
[UIView setAnimationDuration:4];
Tile *currentCard = (Tile*)[tileArray objectAtIndex:i];
currentCard.frame = currentCard.originalFrame;
[UIView commitAnimations];
}
You have two problems: the first is that you're trying to animate the layer's frame directly. Since this is a derived property, you can't do that. Instead, you have to animate the position property. http://developer.apple.com/library/mac/#qa/qa1620/_index.html
Second, you're using UIView's +beginAnimations API, but you say your Tile objects are CALayers, not UIViews. So you don't need to use +beginAnimations. Instead you need to use a CAAnimation object, like CABasicAnimation (untested):
for (Tile *tile in tileArray)
{
static NSString * const kProperty = #"position";
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:kProperty];
animation.duration = 4.0f;
animation.fromValue = [tile valueForKey:kProperty];
animation.toValue = [NSValue valueWithCGRect:tile.originalFrame];
[tile addAnimation:animation forKey:kProperty];
}

Animate between two states of a view, using custom animation

I would like to animate between two states of a view. Say, for example, I have a view with a label and when I change the text of the label an animation renders that change as a page flipping.
Now you can of course do this with a [UIView setAnimationTransition:forView:cache:]:
- (IBAction)nextPage {
[UIView beginAnimation:nil context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp forView:pageView cache:NO];
label.text = #"page 2";
[UIView commitAnimations];
}
- (IBAction)previousPage {
[UIView beginAnimation:nil context:nil];
[UIView setAnimationDuration:1];
[UIView setAnimationTransition:UIViewAnimationTransitionCurlDown forView:pageView cache:NO];
label.text = #"page 1";
[UIView commitAnimations];
}
...but then you cannot use your own custom animation, you are stuck to the built-in animations (they're nice but they're not tailored to my needs).
So the other option is to add a CAAnimation to the view's layer:
- (IBAction)nextPage {
CAAnimation *animation = [self animationWithDuration:1 forward:NO];
[pageView.layer addAnimation:animation forKey:#"pageTransition"];
label.text = #"page 2";
}
- (IBAction)previousPage {
CAAnimation *animation = [self animationWithDuration:1 forward:YES];
[pageView.layer addAnimation:animation forKey:#"pageTransition"];
label.text = #"page 1";
}
Then you are free to set whatever animation Core Animation enables you to do. This works well if I define a CATransition animation, for example a kCATransitionReveal: a view in the "page 2" state appears below the view in the "page 1" state as it slips out.
- (CAAnimation *)animationWithDuration:(float)duration forward:(BOOL)forward {
CATransition *animation = [CATransition animation];
[animation setType:kCATransitionReveal];
[animation setSubtype:forward?kCATransitionFromLeft:kCATransitionFromRight];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
animation.duration = duration;
return animation;
}
But when I define the animation to be for example a CABasicAnimation, only one state of the view is visible.
- (CAAnimation *)animationWithDuration:(float)duration forward:(BOOL)forward {
CATransform3D transform = CATransform3DIdentity;
transform.m34 = 1.0 / -1000;
transform = CATransform3DRotate(transform, M_PI, 0.0f, 1.0f, 0.0f);
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform"];
if(forward) {
animation.fromValue = [NSValue valueWithCATransform3D:transform];
} else {
animation.toValue = [NSValue valueWithCATransform3D:transform];
}
animation.duration = duration;
return animation;
}
Instead, I would like the view in the "page 1" state to remain visible until the end of the animation while the view in the "page 2" state comes into frame, exactly as it behaves with a transition animation.
Of course, I could always mess with duplicating the view and having one appear as a sibling view while I animate the frontmost one and remove it from superview on completion... but there must be a much more straight way to achieve this rather simple animation effect without messing with the views.
Probably something to tell the layer, but then I don't know what, and that's where I need your help, guys :)
Thanks!
I recently did a custom slide transition between subviews in a controller. My approach was to grab a bitmap of the view-out and the view-in, add them to a view and animate that view. At the end you can just remove the transition view and show the view-in. Here's a snippet the transition method:
-(void) transitionToView:(UIView *)viewIn lastView:(UIView *)viewOut slideRight:(BOOL)isRight {
UIGraphicsBeginImageContext(viewOut.frame.size);
UIImageView *bitmapOut = [[UIImageView alloc] initWithFrame: viewOut.frame];
[viewOut.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewOutImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
bitmapOut.image = viewOutImage;
UIImageView *bitmapIn = [[UIImageView alloc] initWithFrame: viewIn.frame ];
bitmapIn.frame = CGRectMake(isRight ? 320 : -320, bitmapIn.frame.origin.y, bitmapIn.frame.size.width, bitmapIn.frame.size.height);
UIGraphicsBeginImageContext(viewIn.frame.size);
[viewIn.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewInImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
bitmapIn.image = viewInImage;
[self.view addSubview:transitionContainer];
[transitionContainer addSubview:bitmapIn];
[transitionContainer addSubview:bitmapOut];
[self removeAllViews];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:0.5f];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(swapViewsAtEndOfTransition:)];
transitionContainer.frame = CGRectMake(isRight ? -320 : 320, 0, 640, 411);
[UIView commitAnimations];
[bitmapOut release]; bitmapOut = nil;
[bitmapIn release]; bitmapIn = nil;}
Then, in the swapViewsAtEndOfTransition selector you can update the views accordingly.
Well, you can create a layer, set it content to an image representing your view final state, than apply the animation to this layer. When the animation is done you can remove the layer and switch view state.
I think this will work better than instancing whole view.
Years have passed but I guess I found a solution for you. Just use kCATransitionPush instead of kCATransitionReveal. Like this:
- (CAAnimation *)animationWithDuration:(float)duration forward:(BOOL)forward {
CATransition *animation = [CATransition animation];
[animation setType:kCATransitionPush];
[animation setSubtype:forward?kCATransitionFromLeft:kCATransitionFromRight];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]];
animation.duration = duration;
return animation;
}

Resources