How to change the rectangle shape to a circle shape with animation - ios

I have a label:
self.logInLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 300, 100)];
self.logInLabel.backgroundColor = [UIColor clearColor];
self.logInLabel.backgroundColor = Color_Circle_Outer;
self.logInLabel.textColor = Color_Circle_Outer;
self.logInLabel.textAlignment = NSTextAlignmentCenter;
self.logInLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.logInLabel.numberOfLines = 1;
self.logInLabel.font = [UIFont boldSystemFontOfSize:12*IPAD];
self.logInLabel.text = #"Log In";
[self addSubview:self.logInLabel];
self.logInLabel.layer.cornerRadius = self.frame.size.height/2.0;
self.logInLabel.layer.borderColor = Color_Circle_Outer.CGColor;
self.logInLabel.layer.borderWidth = 2*IPAD;
self.logInLabel.layer.masksToBounds = YES;
I want to change this shape to a Circle. I have tried:
[UIView animateWithDuration:AnimationTime animations:^{
self.logInLabel.layer.bounds = CGRectMake(0, 0, self.frame.size.height, self.frame.size.height);
}];
But the shape changed immediately without animation, then the position change to center with animation. Then I tried with this:
[UIView animateWithDuration:AnimationTime animations:^{
self.logInLabel.bounds = CGRectMake(0, 0, self.logInLabel.frame.size.height, self.logInLabel.frame.size.height);
}];
I have very ugly animation effect.
I do not care if this view is a label, I just want to implement changing a rectangle shape to a circle shape with an animation.
Thank you.

It works only CABasicAnimation not the UIView animateWithDuration
UIView *logInLabel = [[UIView alloc] initWithFrame:CGRectMake(100, 100, 200, 200)];
logInLabel.backgroundColor = [UIColor blackColor];
logInLabel.layer.masksToBounds = YES;
[self.view addSubview:logInLabel];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"cornerRadius"];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat:100.0f];
animation.duration = 5.0;
[logInLabel.layer addAnimation:animation forKey:#"cornerRadius"];
[logInLabel.layer setCornerRadius:0.0];

From your code remove the line :
self.logInLabel.layer.cornerRadius = self.logInLabel.frame.size.height / 2.0;
and put it in an animation block like this
You can set cornerRadius of your UILabel to get a circular view.
[UIView animateWithDuration:AnimationTime animations:^{
self.logInLabel.layer.cornerRadius = self.logInLabel.frame.size.height / 2.0f;
}];
However, the animation will not look very good and the final frame will not be an exact circle because the height and width of your UILabel are not in 1:1 proportion.
If you still don't get the animation try to check the value of AnimationTime variable.

Look that might help you
[UIView animateWithDuration:0.4 animations:^{
CGPoint center = self.logInLabel.center;
self.logInLabel.layer.cornerRadius = self.logInLabel.frame.size.height/2;
self.logInLabel.frame = CGRectMake(0, 0, self.logInLabel.frame.size.height, self.logInLabel.frame.size.height);
self.logInLabel.center = center;
}];

Related

"Glitch" during UIView Intro CAAnimation

I'm trying to add an Animation to a UIView. The objective of the animation is to "animate" the View appearing on screen (instead of just appearing there).
The animation is all size scaling: start from 5%, go up to 120% and then very quickly back to 100% of the regular scale.
My issue is that the full scale UIView appears very quickly, before the animation starts.
Here's the code:
UIView * myView = [[UIView alloc] initWithFrame:someFrame];
[self.view addSubview:myView];
[self initialAnimationFor:myView];
-(void) initialAnimationFor:(UIView*)pView {
const CFTimeInterval firstDuration = 0.75f;
const CFTimeInterval secondDuration = 0.025f;
const float initialValue = 0.05f;
const float middleValue = 1.20f;
CABasicAnimation * firstAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
firstAnimation.duration = firstDuration;
firstAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
firstAnimation.fromValue = [NSNumber numberWithFloat:initialValue];
firstAnimation.toValue = [NSNumber numberWithFloat:middleValue];
CABasicAnimation * secondAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale"];
secondAnimation.duration = secondDuration;
secondAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
secondAnimation.fromValue = [NSNumber numberWithFloat:middleValue];
secondAnimation.toValue = [NSNumber numberWithFloat:1.0f];
CAAnimationGroup *animationGroup = [CAAnimationGroup new];
animationGroup.duration = firstDuration + secondDuration;
animationGroup.animations = #[firstAnimation, secondAnimation];
[pView.layer addAnimation:animationGroup forKey:nil];
}
Any ideas? Thanks!
I would do a different technique and use chained UIView block animations, like this:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(40, 40, 200, 200)];
myView.backgroundColor = [UIColor redColor];
[self initialAnimationFor:myView];
}
- (void)initialAnimationFor:(UIView*)pView {
pView.transform = CGAffineTransformMakeScale(0.05f, 0.05f);
if (pView.superview == nil) {
[self.view addSubview:pView];
}
[UIView
animateWithDuration:0.75f
animations:^{
pView.transform = CGAffineTransformMakeScale(1.20f, 1.20f);
}
completion:^(BOOL finished) {
[UIView
animateWithDuration:0.25f // <-- Your old value of 0.025f makes the animation VERY quick
animations:^{
pView.transform = CGAffineTransformIdentity;
}
];
}
];
}
With this setup, you get the "grow to slightly larger than 100% and then 'settle' to 100%" effect.
Is this a workable solution?

UIButton rotate with CABasicAnimation does not move initial touch coordinates

I have a UIView and i added a UIButton on it. I am rotating UIView by 180 degrees. After rotating UIView, everything inside it looks rotated including the button. But when i tap on button. it receives touch event only from its initial position. Not from the position it appears on screen. This is my code -
CABasicAnimation *fullRotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
fullRotation.fromValue = [NSNumber numberWithFloat:0];
fullRotation.toValue = [NSNumber numberWithFloat:((180*M_PI)/180)];
fullRotation.speed = 1.0;
fullRotation.autoreverses = NO;
fullRotation.removedOnCompletion = NO;
fullRotation.fillMode = kCAFillModeForwards;
[_subView.layer addAnimation:fullRotation forKey:#"360"];
You can't do it from Interface Builder. You have to rotate it from your code. Like this,
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 100, 200)];
[button addTarget:self action:#selector(selectedButton) forControlEvents:UIControlEventTouchUpInside];
[button setBackgroundColor:[UIColor purpleColor]];
CABasicAnimation *fullRotation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
fullRotation.fromValue = [NSNumber numberWithFloat:0];
fullRotation.toValue = [NSNumber numberWithFloat:((180*M_PI)/180)];
fullRotation.speed = 1.0;
fullRotation.autoreverses = NO;
fullRotation.removedOnCompletion = NO;
fullRotation.fillMode = kCAFillModeForwards;
[button.layer addAnimation:fullRotation forKey:#"360"];
[self.view addSubview:button];
Now the selectedButton will receive every rotated UIButton touch.

How can I know current animation in CAAnimationGroups?

Now I want to use Core Animation to write some methods for animation just as easy as Cocos2d ,just like ccMove,ccFade,ccRotate.
Everything seems ok, but when I want to implement the animation sequence,I've troubled.The first thing I've thought was CCAnimationGroup,but when animations in groups,I can not kown when the current action complete, so I can't set the property of layer, this lead to the layer back to the original status. Anyone know this ? My code:
UIView *testView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
testView.backgroundColor= [UIColor redColor];
testView.center = self.view.center;
//mark1: textview's position is self.view.center
[self.view addSubview:testView];
CAKeyframeAnimation *moveAnimation = [CAKeyframeAnimation
animationWithKeyPath:#"position"];
moveAnimation.duration = 3;
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 100, 100);
CGPathAddLineToPoint(path, NULL, 200, 200);
moveAnimation.path = path;
//mark2: I want the testview move to CGPointMake(100,200)
CGPathRelease(path);
CABasicAnimation *angleAnimation =
[CABasicAnimation
animationWithKeyPath:#"transform.rotation"];
angleAnimation.toValue = #(45 * M_PI / 180);
angleAnimation.duration = 3;
angleAnimation.beginTime = 3; //make3: rotate testview 45°
CAAnimationGroup *actionGroup = [CAAnimationGroup animation];
[actionGroup setDuration:6];
[actionGroup setAnimations:#[moveAnimation, angleAnimation]];
[CATransaction begin];
[testView.layer addAnimation:actionGroup forKey:nil];
[CATransaction commit];
You can run it, and you'll see the textview's position and rotate is not I want.
Can someone help me?
I've solved it.In fact,I don't need to know when one of the actions in group complete.Just set the animation's fillMode and removedOnCompletion, so that the the layer would not back to original status.
UIView *testView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 50, 50)];
testView.backgroundColor= [UIColor redColor];
testView.center = self.view.center;
[self.view addSubview:testView]; //mark1: textview's position is self.view.center
CAKeyframeAnimation *moveAnimation = [CAKeyframeAnimation animationWithKeyPath:#"position"];
moveAnimation.duration = 3;
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 100, 100);
CGPathAddLineToPoint(path, NULL, 200, 200);
moveAnimation.path = path;
moveAnimation.fillMode = kCAFillModeForwards;
moveAnimation.removedOnCompletion = NO;
CGPathRelease(path); //mark2: I want the testview move to CGPointMake(100,200)
CABasicAnimation *angleAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
angleAnimation.toValue = #(45 * M_PI / 180);
angleAnimation.duration = 3;
angleAnimation.beginTime = 3; //make3: rotate testview 45°
angleAnimation.fillMode = kCAFillModeForwards;
angleAnimation.removedOnCompletion = NO;
CAAnimationGroup *actionGroup = [CAAnimationGroup animation];
[actionGroup setDuration:6];
[actionGroup setAnimations:#[moveAnimation, angleAnimation]];
actionGroup.fillMode = kCAFillModeForwards;
actionGroup.removedOnCompletion = NO;
[CATransaction begin];
[testView.layer addAnimation:actionGroup forKey:nil];
[CATransaction commit];
In code above, I set all the action's fillmode to kCAFillModeForwards, and removedOnCompletion = NO.This just work. And I also find that scaleAnimation and fadeAnimation should set their fillmode to kCAFillModeBoth so that the layer cannot back to original property status.
You can also check the code on my github.https://github.com/Vienta/SSAnimation Thank you!
CAAnimationGroup is subclass of CAAnimation, so you can have a delegate of CAAnimationGroup and receive notification when animation stoped.
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag

Animating UIView shape and its content

In an iPhone app how would you animate the shape of an UIView, for example changing from a rectangle into a circle?
I've tried with:
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"path"];
animation.duration = 20.0;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.fromValue = (__bridge_transfer id)aPath;
animation.toValue = (__bridge_transfer id)anotherPath;
[myShapeLayer addAnimation:animation forKey:#"animatePath"];
where myShapeLayer is an instance of CAShapeLayer and aPath and anotherPath CGMutablePathRef.
It works but the view content is not animated as well.
I need to transform a view into a circle and then let it shrink until it disappears.
Try something like this:
Where animeView is your UIView
CABasicAnimation *anim1 = [CABasicAnimation animationWithKeyPath:#"cornerRadius"];
anim1.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
anim1.fromValue = [NSNumber numberWithFloat:0.0f];
anim1.toValue = [NSNumber numberWithFloat:50.0f]; //Half the size of your UIView
anim1.duration = 2.0;
[animeView.layer addAnimation:anim1 forKey:#"cornerRadius"];
[UIView animateWithDuration:10.0 delay:2 options:UIViewAnimationOptionCurveEaseInOut animations:^{
animeView.layer.cornerRadius = 50; //Half the size of your UIView
CGRect reduceRect = animeView.frame;
reduceRect.size.height = 0;
reduceRect.size.width = 0;
[animeView setFrame:reduceRect];
animeView.alpha = 0;
} completion:nil];
Might need some tweaks for you here and there ;-)
EDIT 1:
Ok so how about using two UIView animations?
The first will shrink, strech and move your view.
The second will shrink, slink and remove your view.
[UIView animateWithDuration:2.0 delay:0.5 options:UIViewAnimationOptionCurveEaseIn animations:^{
CGRect moveRect = animeView.frame;
moveRect.origin.x = 0;
moveRect.origin.y = (animeView.center.y -20); //Half the size of height reduction
moveRect.size.height = (animeView.bounds.size.height -40); // height reduction
moveRect.size.width = (animeView.bounds.size.width +20);
[animeView setFrame:moveRect];
} completion:^(BOOL finished){
[UIView animateWithDuration:2.0 delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
CGRect reduceRect = animeView.frame;
reduceRect.size.height = 0;
reduceRect.size.width = 0;
reduceRect.origin.x = -50;
reduceRect.origin.y = animeView.center.y;
animeView.alpha = 0;
[animeView setFrame:reduceRect];
} completion:nil];
}];
EDIT 2:
A answer to you question in the comments:
You can execute animations simultaneous by creating a CAAnimationGroup.
Also I'm using a image to create the resize of content effect.
Example:
//Create a screenshot of your UIView... Still animeView in this example
UIGraphicsBeginImageContext(animeView.bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[animeView.layer renderInContext:context];
UIImage *screenShot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//Add the image as subview:
UIImageView * imageView = [[UIImageView alloc] initWithImage:screenShot];
[animeView addSubview:imageView];
//A cornerRadius animation:
CABasicAnimation *radiusAni = [CABasicAnimation animationWithKeyPath:#"cornerRadius"];
radiusAni.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
radiusAni.fromValue = [NSNumber numberWithFloat:0.0f];
radiusAni.toValue = [NSNumber numberWithFloat:50.0f];
//A stretch animation:
CABasicAnimation *stretchAni = [CABasicAnimation animationWithKeyPath:#"transform.scale.x"];
stretchAni.fromValue = [NSNumber numberWithDouble:1];
stretchAni.toValue = [NSNumber numberWithDouble:(animeView.frame.size.width+100)/animeView.frame.size.width];
//A slide animation:
CABasicAnimation *slideAni = [CABasicAnimation animationWithKeyPath:#"transform.translation.x"];
slideAni.fromValue = [NSNumber numberWithDouble:0];
slideAni.toValue = [NSNumber numberWithDouble:-100];
//A opacity animation:
CABasicAnimation *opacityAni = [CABasicAnimation animationWithKeyPath:#"opacity"];
opacityAni.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
opacityAni.fromValue = [NSNumber numberWithFloat:1];
opacityAni.toValue = [NSNumber numberWithFloat:0];
//The animationgroup
CAAnimationGroup *animGroup = [CAAnimationGroup animation];
//Add them to the group:
[animGroup setAnimations:[NSArray arrayWithObjects:radiusAni, opacityAni, slideAni, stretchAni, nil]];
//Set the properties:
[animGroup setDuration:3.0];
[animGroup setRemovedOnCompletion:NO];
[animGroup setFillMode:kCAFillModeForwards];
//Execute all the animations in the group:
[animeView.layer addAnimation:animGroup forKey:nil];
Then you'll have 4 animations executing at the same time and the resize of the content when stretching, shrinking or whatever you plan to do ;-)
If you want to animate changes in shape you could use a CAShapeLayer. That takes a CGPath that describes the shape to be drawn. It can only draw 1 path, and that drawing can only use a single line color and a single fill color. You can't draw some lines in color 1 and other lines in color 2, etc.
Once you have a CAShapeLayer you can animate changes to the CGPath object associated with the shape layer. The trick to making it work is that the starting and ending CGPath need to have the same number of control points. If you use a path that has 3 linked bezier paths, the end path needs to use 3 linked bezier paths also, but you can move the points around as much as you want.
I've found through experimentation that you can't animate changes to arcs in bezier curves. The reason is that internally, the system generates a series of bezier curves to create the arc, and there are more bezier segments the larger the arc angle.

Animate UIView height from bottom to top

I'm doing a simple animation of UIView height so that it reveals.
By default it seems to be revealing from top to bottom, and I want it to reveal bottom to top.
I have the UIView anchored to the bottom of the screen.
I'm sure it something simple i'm missing..... any tips?
Thanks
I really think the simplest way to accomplish this would be to animate BOTH the height and the y properties of the view. If they happen along the same curve, it should look completely seamless to the user. As you are animating the height to 0, also animate the y component to the original y + the original height.
UIView *view = ...;
float originalY = view.frame.origin.y;
float originalH = view.bounds.size.height;
[UIView animateWithDuration:1.2f delay:1.0f options:UIViewAnimationCurveEaseInOut animations:^{
view.frame = CGRectMake(view.frame.origin.x, (originalY + originalH), view.bounds.size.width, 0);
}completion:^(BOOL finished) {
NSLog(#"Animation is complete");
}];
I believe this would give the look and feel of a collapsing view. I haven't tried this out in code, but I see no reason why it wouldn't be possible like this.
hide under bottom
[self animateViewHeight:myView withAnimationType:kCATransitionFromBottom];
for reverse animation
[self animateViewHeight:myView withAnimationType:kCATransitionFromTop];
...
- (void)animateViewHeight:(UIView*)animateView withAnimationType:(NSString*)animType {
CATransition *animation = [CATransition animation];
[animation setType:kCATransitionPush];
[animation setSubtype:animType];
[animation setDuration:0.5];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[[animateView layer] addAnimation:animation forKey:kCATransition];
animateView.hidden = !animateView.hidden;
}
Like a dog with a bone I figured this out....
Instead of animating the frame height, I applied a transform to the view and set the anchor point of the layer.
//set the anchor point to the bottom of the view
[self setAnchorPoint:CGPointMake(0.5, 1.0) forView:hostView];
//Scale the height to close to zero
hostView.transform = CGAffineTransformMakeScale(1, 0.00001);
If I put 0 as the y scale, the view behaves weird.... at the end of the animation i just set it to hidden.
On the way back up I just use the Identity Transform (reset it)
hostView.transform = CGAffineTransformIdentity;
Note that changing my anchor point shifted the position of my view. See this post for the setAnchorPoint method which normalises the view after setting the anchorPoint
Changing my CALayer's anchorPoint moves the view
Instead you could try putting it in a view with clipsToBounds = YES and then animate it from the bottom to the middle of the view, like so:
viewToAnimate.frame = CGRectMake(viewToAnimate.frame.origin.x,
viewToAnimate.superview.frame.size.height,
viewToAnimate.frame.size.width,
viewToAnimate.frame.size.height);
[UIView animateWithDuration:0.5 animations:^{
viewToAnimate.center = viewToAnimate.superview.center;
}];
This way, you don't have to set the height to 0, and it solves any problems with autoresizing within the view.
As requested, this is the code that I'm using... I'm using a CAKeyFrameAnimation, which may be a bit more than what you're looking for. It would probably work the same with a CABasicAnimation, I'm just showing you this code because I already have it written.
-(id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
springLayer = [[CALayer alloc] init];
springLayer.backgroundColor = [UIColor redColor].CGColor;
springLayer.anchorPoint = CGPointMake(0, 1);
springLayer.frame = CGRectMake(125, 285, 100, 115);
[springLayer setNeedsDisplay];
[self.layer addSublayer:springLayer];
[self test];
}
return self;
}
-(void)test {
CAKeyframeAnimation *heightAnim = [[CAKeyframeAnimation alloc] init];
heightAnim.duration = 3;
heightAnim.removedOnCompletion = NO;
heightAnim.fillMode = kCAFillModeForwards;
heightAnim.beginTime = CACurrentMediaTime() + 0.25;
NSMutableArray *v = [[NSMutableArray alloc] init];
NSMutableArray *t = [[NSMutableArray alloc] init];
float dest = 250;
float difference = 135;
while (difference > 1.0) {
[v addObject:[NSNumber numberWithFloat:dest-difference]];
[t addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
difference *= 0.7;
[v addObject:[NSNumber numberWithFloat:dest+difference]];
[t addObject:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
difference *= 0.7;
}
heightAnim.values = v;
heightAnim.timingFunctions = t;
[springLayer addAnimation:heightAnim forKey:#"bounds.size.height"];
}
one way I've done it with an AdWhirlView, hide it below the screen, then animate it up;
AdWhirlView *adWhirlView = [AdWhirlView requestAdWhirlViewWithDelegate:self];
adWhirlView.delegate = self;
adWhirlView.frame = CGRectMake(0, 430+kAdWhirlViewHeight, kAdWhirlViewWidth, kAdWhirlViewHeight);
[self.parentViewController.view insertSubview:adWhirlView belowSubview:self.view];
[UIView beginAnimations:#"AdWhirlIn" context:nil];
[UIView setAnimationDuration:.5];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
adWhirlView.frame = CGRectMake(0, 430, kAdWhirlViewWidth, kAdWhirlViewHeight);
[UIView commitAnimations];

Resources