animate UIButton border thickness - ios

Is it possible to animate the border thickness of a UIButton?
I tried the following but I just get a quick flash between the two thicknesses I'm trying to animate between.
//animate border thickness
UIButton *btn = _barButton;
CALayer *buttonLayer = [btn layer];
[buttonLayer setBorderWidth:0.0f];
[UIView animateWithDuration:1.0f animations:^{
[buttonLayer setBorderWidth:15.0f];
} completion:^(BOOL finished){
[UIView animateWithDuration:1.0f animations:^{
[buttonLayer setBorderWidth:2.5f];
} completion:nil];
}];
EDIT: As per David H's suggestion, this can be done with CABasicAnimation.
UIButton *btn = _barButtons;
CALayer *buttonLayer = [btn layer];
//animate border thickness
CABasicAnimation *animation = [CABasicAnimation animation];
animation.keyPath = #"borderWidth";
animation.fromValue = #0.0;
animation.toValue = #5.0;
animation.duration = 0.25;
[buttonLayer addAnimation:animation forKey:#"basic"];

You can do it, but you'll have to code the animation differently. Look at Apple's "Core Animation Programming Guide", and find the section on "CALayer Animatable Properties". You can see that borderWidth is animatable, but you need to use the default implied CABasicAnimation object, described in that document.
I've done this kind of thing in the past - but it was years ago so foggy on the specific details. But you can make it work for sure.

Related

AVFoundation focus animation using Core Animation

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
}

How can I replace CGAffineTransform scale and alpha animations using CABasicAnimations and Auto Layout?

As far as I can see, Apple wants us to move away from CGAffineTransform animations and into animations using:
myView.layoutConstraint.constant = someNewValue;
[myView layoutIfNeeded];
for animations that involve a translation of a view.
It also seems we should be now using CABasicAnimation animations for scale and rotation (and sometimes opacity) because it animates the view's layer and not the view and in doing so, plays nicely with auto layout.
I used the following code to apply an opacity and scale animation that worked beautifully:
[UIView animateWithDuration:0.1f animations:^{
// first animation
self.myMeButton.alpha = 1;
self.myMeButton.transform = CGAffineTransformMakeScale(1.1, 1.1);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.2f animations:^{
// second animation
self.myButton.transform = CGAffineTransformMakeScale(1, 1);
}];
}];
Of course auto layout plays havoc with the scale animation and so I am trying to find an alternative way to do it. So far, I have come up with this:
[CATransaction begin]; {
[CATransaction setCompletionBlock:^{
// code for when animation completes
self.pickMeButton.alpha = 1;
CABasicAnimation *scaleDown = [CABasicAnimation animationWithKeyPath:#"scale"];
scaleDown.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1, 1.1, 1)];
scaleDown.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1)];
scaleDown.duration = 0.1;
[self.myButton.layer addAnimation:scaleDown forKey:nil];
}];
// describe animations:
CABasicAnimation* scaleUp = [CABasicAnimation animationWithKeyPath:#"scale"];
scaleUp.autoreverses = NO;
scaleUp.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1)];
scaleUp.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.1, 1.1, 1)];
CABasicAnimation *fadeAnim = [CABasicAnimation animationWithKeyPath:#"opacity"];
fadeAnim.fromValue=[NSNumber numberWithDouble:0.0];
fadeAnim.toValue=[NSNumber numberWithDouble:1.0];
// Customization for all animations:
CAAnimationGroup *group = [CAAnimationGroup animation];
group.duration = 0.2f;
group.repeatCount = 1;
group.autoreverses = NO;
group.animations = #[scaleUp, fadeAnim];
// add animations to the view's layer:
[self.myButton.layer addAnimation:group forKey:#"allMyAnimations"];
} [CATransaction commit];
As you can see the code almost 3 times as long as before and the animation on the device is noticeably less 'smooth' than it was previously.
Is there any way to do this better?
Thanks in advance for your response.
EDIT: This seems to have done the trick in that the animations are smooth, but I still feel like the code for this could be a lot more succinct.
[UIView animateWithDuration:0.2f animations:^{
self.pickMeButton.alpha = 1;
CABasicAnimation* scaleUp = [CABasicAnimation animationWithKeyPath:#"transform"];
scaleUp.duration = 0.2;
scaleUp.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(0.1, 0.1, 1)];
scaleUp.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.2, 1.2, 1)];
[self.pickMeButton.layer addAnimation:scaleUp forKey:nil];
}completion:^(BOOL finished) {
CABasicAnimation* scaleDown = [CABasicAnimation animationWithKeyPath:#"transform"];
scaleDown.duration = 0.1;
scaleDown.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1.2, 1.2, 1)];
scaleDown.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeScale(1, 1, 1)];
[self.pickMeButton.layer addAnimation:scaleDown forKey:nil];
}];
I don't know why you want to do it with CABasicAnimation for scale. You can do it like you mention at the top of your question. Set a new value for the view's width and height constraint constant values and then use [myView layoutIfNeeded] inside animateWithDuration. If the view doesn't have height and width constraints, but has constants to the top and bottom and/or left and right edges of the superview, change those values instead.

UITextField being clipped during CABasicAnimation with keyPath of bounds.size.width

I have a somewhat of an odd issue on hand. I have UITextField. This TextField has it’s width resized when a button is tapped. To do that I first did the following:
CGRect newbounds = originalBounds;
newbounds.size.width = newbounds.size.width/2;
[UIView beginAnimations:nil context:nil];
self.textField2.bounds = newbounds;
[UIView commitAnimations];
Easy enough and it worked. Now I move along and try to exert a bit more control over the animation. To do that I go the next level up to CABasicAnimation using the following:
CGRect newbounds = originalBounds;
newbounds.size.width = newbounds.size.width/2;
CABasicAnimation *animShrink = [CABasicAnimation animation];
animShrink.keyPath = #"bounds";
animShrink.toValue = [NSValue valueWithCGRect:newbounds];
animShrink.duration = 0.5;
animShrink.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animShrink.delegate = self;
[self.textField2.layer addAnimation:animShrink forKey:#"shrink"];
And that is when I run into the clipping issue during the animation as can be seen at: http://coldstorage.macbonsai.com/public/cabasic_animation_clipping.png (since I “need at least 10 reputation to post images” as per Stackoverflow).
So I tried the following version of the animation:
CGRect newbounds = originalBounds;
newbounds.size.width = newbounds.size.width/2;
CABasicAnimation *animShrink = [CABasicAnimation animation];
animShrink.keyPath = #"bounds.size.width";
animShrink.fromValue = [NSNumber numberWithFloat:originalBounds.size.width];
animShrink.toValue = [NSNumber numberWithFloat:newbounds.size.width];
animShrink.duration = 0.5;
animShrink.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animShrink.delegate = self;
[self.textField2.layer addAnimation:animShrink forKey:#"shrink"];
Still encountered the same issue. At this point I removed all constraints from the UITextField and tried both the CABasicAnimation versions. Same issue. At this point I don’t know what else to do. Any help will be greatly appreciated.
Many thanks in advance.
If you change the style of the button to UITextBorderStyleNone it doesn't get clipped.
My guess is that when the style is UITextBorderStyleRoundedRect, Apple probably just adds a UIImageView to the button with the "round rect" image, so when you animate the button's layer, its subviews don't animate together, because in iOS you have to explicitly animate all sublayers.

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