I want to rotate an UIImageView, it works in iOS8 but not in iOS7. Here's the code, I'm doing,
- (void)awakeFromNib {
[self createAnimation];
}
- (void)animationStart {
[self.animationImageView.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}
- (void)animationStop {
[self.animationImageView.layer removeAnimationForKey:#"rotationAnimation"];
}
- (void)createAnimation {
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: -M_PI * 2.0 ];
rotationAnimation.duration = 0.7;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = CGFLOAT_MAX;
}
use this code to image rotate,
- (void)startSpinImage
{
if ([_img.layer animationForKey:#"SpinAnimation"] == nil)
{
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat: 2*M_PI];
animation.duration = 10.0f;
animation.repeatCount = INFINITY;
[_img.layer addAnimation:animation forKey:#"SpinAnimation"];
}
}
- (void)stopSpinImage
{
[_img.layer removeAnimationForKey:#"SpinAnimation"];
}
Try using
[animationImageView startAnimating];
[animationImageView stopAnimating];
Related
I have a CAShapeLayer where I animate a circle. The animation is to first "undraw" the circle clockwise and then redraw the circle clockwise. Sort of a "rotating circle". Another way to put it: Move path stroke end point to start, then move the start point to the end.
The animation itself works, but it produces glitches now and then. It manifests in a short glimpse of the entire circle when it is supposed to be "undrawn".
Why does this occur and how can you fix it?
Thanks,
// Shape creation
layer.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, self.width - 2 * OUTER_BORDER_WIDTH, self.width - 2* OUTER_BORDER_WIDTH)].CGPath;
// Animation queuing
-(void) applyNextAnimation
{
CABasicAnimation* animation;
if (self.animatingOpening)
{
animation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat:1.0f];
self.animatingOpening = NO;
}
else
{
animation = [CABasicAnimation animationWithKeyPath:#"strokeStart"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat:1.0f];
self.animatingOpening = YES;
}
animation.duration = 1.0f;
animation.autoreverses = NO;
animation.delegate = self;
animation.removedOnCompletion = YES;
[self.outerCircleLayer addAnimation:animation forKey:#"stroke"];
}
// Animation stop callback
-(void) animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if (self.isAnimating)
{
[self applyNextAnimation];
}
}
It blinks becuase you are not setting the corresponding property on the layer. So when the animation completes, the layer's model is still in the pre-animated state and that is what you see momentarily between the two animations.
This will get you towards what you want...
if (self.animatingOpening)
{
self.outerCircleLayer.strokeStart = 0.0;
animation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat:1.0f];
self.animatingOpening = NO;
}
else
{
self.outerCircleLayer.strokeStart = 1.0;
animation = [CABasicAnimation animationWithKeyPath:#"strokeStart"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat:1.0f];
self.animatingOpening = YES;
}
animation.duration = 1.0f;
animation.autoreverses = NO;
that almost works, but you will notice a more subtle glitch as you transition from the undrawn state to start animating the drawing state. The beginning of the circle has a small reverse animation as it starts. This is an implicit animation triggered by setting strokeStart from 1.0 to 0.0: which you need to get rid of so that all of the animations effects are under your control. You can achieve that most simply by setting disableActions to YES on CATransaction:
[CATransaction setDisableActions:YES];
( add it just above if (self.animatingOpening))
I implemented corePlot in xcode and I'm using the pie chart. I'm trying to create a 3d flip animation while the chart reloads. Here is the code:
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale.x"];
scaleAnimation.fromValue = [NSNumber numberWithFloat:1.0];
scaleAnimation.toValue = [NSNumber numberWithFloat:0.5];
scaleAnimation.duration = 1.0f;
scaleAnimation.removedOnCompletion = NO;
[self.pieChart addAnimation:scaleAnimation forKey:#"scale"];
[self.pieChart reloadData];
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.scale.x"];
animation.fromValue = [NSNumber numberWithFloat:0.5];
animation.toValue = [NSNumber numberWithFloat:1.0];
animation.duration = 1.0f;
[self.pieChart addAnimation:animation forKey:#"scale"];
I didn't get desirable effects. I think what's happening is, both animations are happening at once. (Though I'm not sure that is what's happening.)
Also, is it possible to add z-depth? If so, how?
Update
I tried the follwoing:
CABasicAnimation *currentAnition = (CABasicAnimation *)anim;
if (currentAnition == self.scaleAnimation {...}
And it didn't work.
CAAnimation is KVC compliant, so we can store whether you're starting or finishing for lookup in the delegate methods:
{
CABasicAnimation *scaleAnimation = [CABasicAnimation animationWithKeyPath:#"transform.scale.x"];
[scaleAnimation setValue:#(YES) forKey:#"scaleAnimation"];
// set the delegate
scaleAnimation.delegate = self;
scaleAnimation.fromValue = [NSNumber numberWithFloat:1.0];
scaleAnimation.toValue = [NSNumber numberWithFloat:0.5];
scaleAnimation.duration = 1.0f;
scaleAnimation.removedOnCompletion = NO;
[self.pieChart addAnimation:scaleAnimation forKey:#"scale"];
[self.pieChart reloadData];
}
- (void)runSecondAnimation
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"transform.scale.x"];
[animation setValue:#(YES) forKey:#"secondAnimation"];
animation.delegate = self;
animation.fromValue = [NSNumber numberWithFloat:0.5];
animation.toValue = [NSNumber numberWithFloat:1.0];
animation.duration = 1.0f;
[self.pieChart addAnimation:animation forKey:#"scale"];
}
/* Called when the animation either completes its active duration or
* is removed from the object it is attached to (i.e. the layer). 'flag'
* is true if the animation reached the end of its active duration
* without being removed. */
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
BOOL scaleAnimation = [[anim valueForKey:#"scaleAnimation"] boolValue];
if (scaleAnimation) {
[self runSecondAnimation];
}
BOOL secondAnimation = [[anim valueForKey:#"secondAnimation"] boolValue];
if (secondAnimation) {
[self runThirdAnimation];
}
}
And remember you have also:
/* Called when the animation begins its active duration. */
- (void)animationDidStart:(CAAnimation *)anim;
there.
in iOS app, Core animation callback don't work.
- (void)startAnim {
CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
anim.fromValue = startAngle;
anim.toValue = endAngle;
anim.duration = 2;
anim.delegate = self;
[self.target addAnimation:anim forKey:nil]; // self.target is CALayer instance, it's sublayer of Custom UIView
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
[self.target setValue:#(endAngle) forKeyPath:#"transform.rotation.z"];
}
But animationDidStop never be called.
If I change the code like as following, completion blocked is called.
- (void)startAnim {
CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
anim.fromValue = startAngle;
anim.toValue = endAngle;
anim.duration = 2;
anim.delegate = self;
[CATransaction begin];
[CATransaction setCompletionBlock:^{
[self.target setValue:#(endAngle) forKeyPath:#"transform.rotation.z"];
}];
[self.target addAnimation:anim forKey:nil];
[CATransaction commit];
}
But I don't want to use CATransaction.
Why is not animationDidStop called?
Update:
There is a way to set final value like as
- (void)startAnim {
CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
anim.fromValue = startAngle;
anim.toValue = endAngle;
anim.duration = 2;
anim.delegate = self;
[self.target setValue:#(endAngle) forKeyPath:#"transform.rotation.z"];
[self.target addAnimation:anim forKey:nil];
}
But final assignment should be done when the animation is finished. Because there are multiple dynamic animations of layer, so I don't know final value.
I found the reason why animationDidStop is not called.
Because animation was added in loop of other thread,
So I fixed like as following.
- (void)startAnim {
dispatch_async(dispatch_get_main_queue(), ^{
CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
anim.fromValue = startAngle;
anim.toValue = endAngle;
anim.duration = 2;
anim.delegate = self;
[self.target addAnimation:anim forKey:nil];
});
}
To me it sounds like you dont want the animation to reset its position, this is quite simple and achieved with a couple lines of code when setting up the animation.
It can be easily placed in your code like such:
anim.fillMode = kCAFillModeForwards;
anim.removedOnCompletion = NO;
What this means is when your animation has finished it will remain at the end and any further animations will be from that state.
Last year I use animation of UIView instead CABasicAnimation, but if my memory does not fail you have to set endAngle befor add animation, so try this:
- (void)startAnim {
CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
anim.fromValue = startAngle;
anim.toValue = endAngle;
anim.duration = 2;
anim.delegate = self;
[self.target setValue:#(endAngle) forKeyPath:#"transform.rotation.z"];
[self.target addAnimation:anim forKey:nil];
}
UPD:
You are setting anim.toValue = endAngle; before start animation, so its not good that end value changes after animation complete. Anyway you can set it again in animationDidStop
- (void)startAnim {
CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
anim.fromValue = startAngle;
anim.toValue = endAngle;
anim.duration = 2;
anim.delegate = self;
intermediateAngle = endAngle;
[self.target setValue:#(endAngle) forKeyPath:#"transform.rotation.z"];
[self.target addAnimation:anim forKey:nil];
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
if (intermediateAngle != endAngle) {
startAngle = intermediateAngle;
[self startAnim]; // or just [self.target setValue:#(endAngle) forKeyPath:#"transform.rotation.z"];
}
}
I am wondering if there is a way to quickly use more different timing functions across an app. For instance using Robert Penner's, many toolkits expose them and I am surprised Apple doesn't.
Basically a way to have a block based command as flexible as animateWithDuration:delay:options:animations:completion: in UIView.
I come up with this solution and it would be great to know if there are better alternatives.
// UIView+CustomTimingFunction.h
#import <UIKit/UIKit.h>
typedef enum {
CustomTimingFunctionDefault,
CustomTimingFunctionLinear,
CustomTimingFunctionEaseIn,
CustomTimingFunctionEaseOut,
....
} CustomTimingFunction;
#interface UIView (CustomTimingFunction)
- (void)animateProperty:(NSString *)property from:(id)from to:(id)to withDuration:(NSTimeInterval)duration customTimingFunction:(CustomTimingFunction)customTimingFunction competion:(void (^)(void))competion;
#end
and
// UIView+CustomTimingFunction.m
#import "UIView+CustomTimingFunction.h"
#implementation UIView (CustomTimingFunction)
- (void)animateProperty:(NSString *)property from:(id)from to:(id)to withDuration:(NSTimeInterval)duration customTimingFunction:(CustomTimingFunction)customTimingFunction competion:(void (^)(void))competion
{
[CATransaction begin]; {
[CATransaction setCompletionBlock:competion];
if([property isEqualToString:#"frame"]){
CGRect fromRect = [from CGRectValue];
CGPoint fromPosition = CGPointMake(CGRectGetMidX(fromRect), CGRectGetMidY(fromRect));
CGRect toRect = [to CGRectValue];
CGPoint toPosition = CGPointMake(CGRectGetMidX(toRect), CGRectGetMidY(toRect));
CABasicAnimation *positionAnimation = [CABasicAnimation animationWithKeyPath:#"position"];
positionAnimation.timingFunction = [[self class] functionWithType:customTimingFunction];
positionAnimation.fromValue = [NSValue valueWithCGPoint:fromPosition];
positionAnimation.toValue = [NSValue valueWithCGPoint:toPosition];
CABasicAnimation *boundsAnimation = [CABasicAnimation animationWithKeyPath:#"bounds"];
boundsAnimation.timingFunction = [[self class] functionWithType:customTimingFunction];
boundsAnimation.fromValue = from;
boundsAnimation.toValue = to;
CAAnimationGroup * group =[CAAnimationGroup animation];
group.removedOnCompletion=NO;
group.fillMode=kCAFillModeForwards;
group.animations = #[positionAnimation,boundsAnimation];
group.duration = duration;
[self.layer addAnimation:group forKey:property];
}else{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:property];
animation.duration = duration;
animation.timingFunction = [[self class] functionWithType:customTimingFunction];
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.fromValue = from;
animation.toValue = to;
[self.layer addAnimation:animation forKey:animation.keyPath];
}
} [CATransaction commit];
}
+(CAMediaTimingFunction *)functionWithType:(CustomTimingFunction)type
{
switch (type) {
case CustomTimingFunctionDefault:
return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
break;
case CustomTimingFunctionLinear:
return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
break;
case CustomTimingFunctionEaseIn:
return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
break;
case CustomTimingFunctionEaseOut:
return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
break;
case CustomTimingFunctionEaseInOut:
return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
break;
....
}
NSLog(#"Couldn't find CustomTimingFunction, will return linear");
return [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
}
#end
Thanks!
I have some problem with CABasicAnimation. It`s similar to that post: CABasicAnimation rotate returns to original position
So, i have uiimageview that rotate in touchMove. In touchEnd invoke method that do "animation of inertia" :
-(void)animationRotation: (float)beginValue
{
CABasicAnimation *anim;
anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
anim.duration = 0.5;
anim.repeatCount = 1;
anim.fillMode = kCAFillModeForwards;
anim.fromValue = [NSNumber numberWithFloat:beginValue];
[anim setDelegate:self];
anim.toValue = [NSNumber numberWithFloat:(360*M_PI/180 + beginValue)];
[appleView.layer addAnimation:anim forKey:#"transform"];
CGAffineTransform rot = CGAffineTransformMakeRotation(360*M_PI/180 + beginValue);
appleView.transform = rot;
}
This animation works fine, but if I invoke touchBegan before animationRotation ended, angle of rotation is beginValue. I need cath current angle of rotation. As an experiment, i declare method
-(vod) animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
NSLog(#"Animation finished!");
}
and it's seems working. but I don't know how get that value of angle or CGAffineTransform for my UIImageView in animationDidStop.
It's even possible to do? Thanks.
you should use presentationLayer method to get layer properties during animation in flight.
so your code should be like this,
#define RADIANS_TO_DEGREES(__ANGLE__) ((__ANGLE__) / (float)M_PI * 180.0f)
-(void)animationRotation: (float)beginValue
{
CABasicAnimation *anim;
anim = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
anim.duration = 0.5;
anim.repeatCount = 1;
anim.fillMode = kCAFillModeForwards;
anim.fromValue = [NSNumber numberWithFloat:beginValue];
[anim setDelegate:self];
//get current layer angle during animation in flight
CALayer *currentLayer = (CALayer *)[appleView.layer presentationLayer];
float currentAngle = [(NSNumber *)[currentLayer valueForKeyPath:#"transform.rotation.z"] floatValue];
currentAngle = roundf(RADIANS_TO_DEGREES(currentAngle));
NSLog(#"current angle: %f",currentAngle);
anim.toValue = [NSNumber numberWithFloat:(360*M_PI/180 + beginValue)];
[appleView.layer addAnimation:anim forKey:#"transform"];
CGAffineTransform rot = CGAffineTransformMakeRotation(360*M_PI/180 + beginValue);
appleView.transform = rot;
}