UIView无限360度旋转animation?
我想旋转一个UIImageView
360度,并看了几个在线教程。 如果没有UIView
停止,或者跳到一个新的位置,我都不能让他们工作。
- 我怎样才能做到这一点?
我试过的最新的事情是:
[UIView animateWithDuration:1.0 delay:0.0 options:0 animations:^{ imageToMove.transform = CGAffineTransformMakeRotation(M_PI); } completion:^(BOOL finished){ NSLog(@"Done!"); }];
但是,如果我使用2 * pi,它根本不会移动(因为它是相同的位置)。 如果我试图做pi(180度),它可以工作,但如果我再次调用方法,它会向后旋转。
编辑 :
[UIView animateWithDuration:1.0 delay:0.0 options:0 animations:^{ [UIView setAnimationRepeatCount:HUGE_VALF]; [UIView setAnimationBeginsFromCurrentState:YES]; imageToMove.transform = CGAffineTransformMakeRotation(M_PI); } completion:^(BOOL finished){ NSLog(@"Done!"); }];
也不起作用。 它进入180度,暂停一秒钟,然后重置到0度,然后重新开始。
find一个方法(我修改了一下),完全适合我: iPhone UIImageView旋转
#import <QuartzCore/QuartzCore.h> - (void) runSpinAnimationOnView:(UIView*)view duration:(CGFloat)duration rotations:(CGFloat)rotations repeat:(float)repeat { CABasicAnimation* rotationAnimation; rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0 /* full rotation*/ * rotations * duration ]; rotationAnimation.duration = duration; rotationAnimation.cumulative = YES; rotationAnimation.repeatCount = repeat ? HUGE_VALF : 0; [view.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"]; }
理查德·罗斯三世的荣誉,但我发现他的代码不是我所需要的。 我相信, options
的默认值是给你UIViewAnimationOptionCurveEaseInOut
,它在连续animation中看起来不正确。 此外,我添加了一张支票,以便在需要的时候(如果我不需要无限的 ,但是无限的持续时间),我可以在四分之一转的时间内停止animation,并且在最初的90度期间使加速度上升,并且在最后90度(在要求停止之后):
// an ivar for your class: BOOL animating; - (void)spinWithOptions:(UIViewAnimationOptions)options { // this spin completes 360 degrees every 2 seconds [UIView animateWithDuration:0.5 delay:0 options:options animations:^{ self.imageToMove.transform = CGAffineTransformRotate(imageToMove.transform, M_PI / 2); } completion:^(BOOL finished) { if (finished) { if (animating) { // if flag still set, keep spinning with constant speed [self spinWithOptions: UIViewAnimationOptionCurveLinear]; } else if (options != UIViewAnimationOptionCurveEaseOut) { // one last spin, with deceleration [self spinWithOptions: UIViewAnimationOptionCurveEaseOut]; } } }]; } - (void)startSpin { if (!animating) { animating = YES; [self spinWithOptions: UIViewAnimationOptionCurveEaseIn]; } } - (void)stopSpin { // set the flag to stop spinning after one last 90 degree increment animating = NO; }
更新
我添加了处理请求以再次开始旋转的能力( startSpin
),而前一个旋转正在startSpin
(完成)。 在Github上的示例项目。
Nate的上面的答案非常适合停止和启动animation,并提供更好的控制。 我很好奇你为什么不工作,而他呢。 我想在这里分享我的发现和一个简单的代码版本,可以连续animationUIView而不会拖延。
这是我使用的代码,
- (void)rotateImageView { [UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ [self.imageView setTransform:CGAffineTransformRotate(self.imageView.transform, M_PI_2)]; }completion:^(BOOL finished){ if (finished) { [self rotateImageView]; } }]; }
我使用'CGAffineTransformRotate'而不是'CGAffineTransformMakeRotation',因为前者返回保存为animation的结果。 这将防止在animation过程中跳转或重置视图。
另一件事是不使用“UIViewAnimationOptionRepeat”,因为在animation开始重复之前,它会重置使视图跳转回原始位置的变换。 而不是重复,你recursion,以便转换不会重置为原始值,因为animation块实际上永远不会结束。
最后一点是,必须以90度(M_PI / 2)而不是360度或180度(2 * M_PI或M_PI)的步幅来转换视图。 因为变换发生为正弦和余弦值的matrix乘法。
t' = [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] * t
所以,如果使用180度变换,则180的余弦值为-1,使得视图每次都以相反的方向变换(如果将变换的弧度值更改为M_PI,则注意Nate的答案也会出现此问题)。 一个360度的转变只是要求视图保持原来的位置,因此你根本看不到任何旋转。
在Swift中,您可以使用下面的代码进行无限循环:
let kRotationAnimationKey = "com.myapplication.rotationanimationkey" // Any key func rotateView(view: UIView, duration: Double = 1) { if view.layer.animationForKey(kRotationAnimationKey) == nil { let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation") rotationAnimation.fromValue = 0.0 rotationAnimation.toValue = Float(M_PI * 2.0) rotationAnimation.duration = duration rotationAnimation.repeatCount = Float.infinity view.layer.addAnimation(rotationAnimation, forKey: kRotationAnimationKey) } }
停止就像:
func stopRotatingView(view: UIView) { if view.layer.animationForKey(kRotationAnimationKey) != nil { view.layer.removeAnimationForKey(kRotationAnimationKey) } }
如果你想要做的就是无休止地旋转图像,这个工作很好,很简单:
NSTimeInterval duration = 10.0f; CGFloat angle = M_PI / 2.0f; CGAffineTransform rotateTransform = CGAffineTransformRotate(imageView.transform, angle); [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionRepeat| UIViewAnimationOptionCurveLinear animations:^{ imageView.transform = rotateTransform; } completion:nil];
根据我的经验,这个工作完美无瑕,但是要确保你的图像能够在没有任何偏移的情况下围绕它的中心旋转,否则一旦图像animation到达PI,图像animation就会“跳跃”。
改变旋转的方向,改变angle
的符号( angle *= -1
)。
@AlexPretzlav 更新评论使我重新考虑这一点,我意识到,当我写这个时,我旋转的图像沿着垂直和水平轴镜像,这意味着图像确实只旋转了90度,然后重置,虽然它看起来像它一直在旋转。
所以,如果你的图像和我的一样,那么这个效果会很好,但是如果图像不是对称的,你会注意到90度后的“快照”回到原来的方向。
要旋转一个不对称的图像,最好用接受的答案。
其中一个较不优雅的解决scheme,如下所示,将真正旋转图像,但animation重新启动时,可能会有一个明显的口吃:
- (void)spin { NSTimeInterval duration = 0.5f; CGFloat angle = M_PI_2; CGAffineTransform rotateTransform = CGAffineTransformRotate(self.imageView.transform, angle); [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ self.imageView.transform = rotateTransform; } completion:^(BOOL finished) { [self spin]; }]; }
你也可以用块做这个,就像@ richard-j-ross-iii所build议的那样,但是你会得到一个保留循环的警告,因为这个块在捕获自己:
__block void(^spin)() = ^{ NSTimeInterval duration = 0.5f; CGFloat angle = M_PI_2; CGAffineTransform rotateTransform = CGAffineTransformRotate(self.imageView.transform, angle); [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ self.imageView.transform = rotateTransform; } completion:^(BOOL finished) { spin(); }]; }; spin();
我从被检查的解决scheme中select了一个Swift扩展:
extension UIView{ func rotate() { let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z") rotation.toValue = NSNumber(double: M_PI * 2) rotation.duration = 1 rotation.cumulative = true rotation.repeatCount = FLT_MAX self.layer.addAnimation(rotation, forKey: "rotationAnimation") } }
使用四分之一转,并逐渐增加转弯。
void (^block)() = ^{ imageToMove.transform = CGAffineTransformRotate(imageToMove.transform, M_PI / 2); } void (^completion)(BOOL) = ^(BOOL finished){ [UIView animateWithDuration:1.0 delay:0.0 options:0 animations:block completion:completion]; } completion(YES);
我在这个仓库find了很好的代码,
这里是它的代码,我已经根据我的需要做了小小的改变:)
的UIImageView + Rotate.h
#import <Foundation/Foundation.h> @interface UIImageView (Rotate) - (void)rotate360WithDuration:(CGFloat)duration repeatCount:(float)repeatCount; - (void)pauseAnimations; - (void)resumeAnimations; - (void)stopAllAnimations; @end
的UIImageView + Rotate.m
#import <QuartzCore/QuartzCore.h> #import "UIImageView+Rotate.h" @implementation UIImageView (Rotate) - (void)rotate360WithDuration:(CGFloat)duration repeatCount:(float)repeatCount { CABasicAnimation *fullRotation; fullRotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; fullRotation.fromValue = [NSNumber numberWithFloat:0]; //fullRotation.toValue = [NSNumber numberWithFloat:(2*M_PI)]; fullRotation.toValue = [NSNumber numberWithFloat:-(2*M_PI)]; // added this minus sign as i want to rotate it to anticlockwise fullRotation.duration = duration; fullRotation.speed = 2.0f; // Changed rotation speed if (repeatCount == 0) fullRotation.repeatCount = MAXFLOAT; else fullRotation.repeatCount = repeatCount; [self.layer addAnimation:fullRotation forKey:@"360"]; } //Not using this methods :) - (void)stopAllAnimations { [self.layer removeAllAnimations]; }; - (void)pauseAnimations { [self pauseLayer:self.layer]; } - (void)resumeAnimations { [self resumeLayer:self.layer]; } - (void)pauseLayer:(CALayer *)layer { CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; layer.speed = 0.0; layer.timeOffset = pausedTime; } - (void)resumeLayer:(CALayer *)layer { CFTimeInterval pausedTime = [layer timeOffset]; layer.speed = 1.0; layer.timeOffset = 0.0; layer.beginTime = 0.0; CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; layer.beginTime = timeSincePause; } @end
这是为我工作:
[UIView animateWithDuration:1.0 animations:^ { self.imageView.transform = CGAffineTransformMakeRotation(M_PI); self.imageView.transform = CGAffineTransformMakeRotation(0); }];
这是我的迅速解决scheme作为UIView扩展名。 可以将其视为任何UIImageView上的UIActivityIndicator行为的模拟。
import UIKit extension UIView { /** Starts rotating the view around Z axis. @param duration Duration of one full 360 degrees rotation. One second is default. @param repeatCount How many times the spin should be done. If not provided, the view will spin forever. @param clockwise Direction of the rotation. Default is clockwise (true). */ func startZRotation(duration duration: CFTimeInterval = 1, repeatCount: Float = Float.infinity, clockwise: Bool = true) { if self.layer.animationForKey("transform.rotation.z") != nil { return } let animation = CABasicAnimation(keyPath: "transform.rotation.z") let direction = clockwise ? 1.0 : -1.0 animation.toValue = NSNumber(double: M_PI * 2 * direction) animation.duration = duration animation.cumulative = true animation.repeatCount = repeatCount self.layer.addAnimation(animation, forKey:"transform.rotation.z") } /// Stop rotating the view around Z axis. func stopZRotation() { self.layer.removeAnimationForKey("transform.rotation.z") } }
一个Swift3版本:
extension UIView { func startRotate() { let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z") rotation.fromValue = 0 rotation.toValue = NSNumber(value: M_PI * 2) rotation.duration = 2 rotation.isCumulative = true rotation.repeatCount = FLT_MAX self.layer.add(rotation, forKey: "rotationAnimation") } func stopRotate() { self.layer.removeAnimation(forKey: "rotationAnimation") } }
记得在startRotate
中调用startRotate
而不是在viewDidLoad
。
你也可以使用UIView和块做相同types的animation。 这是一个类扩展的方法,可以任意angular度旋转视图。
- (void)rotationWithDuration:(NSTimeInterval)duration angle:(CGFloat)angle options:(UIViewAnimationOptions)options { // Repeat a quarter rotation as many times as needed to complete the full rotation CGFloat sign = angle > 0 ? 1 : -1; __block NSUInteger numberRepeats = floorf(fabsf(angle) / M_PI_2); CGFloat quarterDuration = duration * M_PI_2 / fabs(angle); CGFloat lastRotation = angle - sign * numberRepeats * M_PI_2; CGFloat lastDuration = duration - quarterDuration * numberRepeats; __block UIViewAnimationOptions startOptions = UIViewAnimationOptionBeginFromCurrentState; UIViewAnimationOptions endOptions = UIViewAnimationOptionBeginFromCurrentState; if (options & UIViewAnimationOptionCurveEaseIn || options == UIViewAnimationOptionCurveEaseInOut) { startOptions |= UIViewAnimationOptionCurveEaseIn; } else { startOptions |= UIViewAnimationOptionCurveLinear; } if (options & UIViewAnimationOptionCurveEaseOut || options == UIViewAnimationOptionCurveEaseInOut) { endOptions |= UIViewAnimationOptionCurveEaseOut; } else { endOptions |= UIViewAnimationOptionCurveLinear; } void (^lastRotationBlock)(void) = ^ { [UIView animateWithDuration:lastDuration delay:0 options:endOptions animations:^{ self.transform = CGAffineTransformRotate(self.transform, lastRotation); } completion:^(BOOL finished) { NSLog(@"Animation completed"); } ]; }; if (numberRepeats) { __block void (^quarterSpinningBlock)(void) = ^{ [UIView animateWithDuration:quarterDuration delay:0 options:startOptions animations:^{ self.transform = CGAffineTransformRotate(self.transform, M_PI_2); numberRepeats--; } completion:^(BOOL finished) { if (numberRepeats > 0) { startOptions = UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveLinear; quarterSpinningBlock(); } else { lastRotationBlock(); }NSLog(@"Animation completed"); } ]; }; quarterSpinningBlock(); } else { lastRotationBlock(); } }
如果有人想要奈特的解决scheme,但很快,那么这里是一个粗略的快速翻译:
class SomeClass: UIViewController { var animating : Bool = false @IBOutlet weak var activityIndicatorImage: UIImageView! func startSpinning() { if(!animating) { animating = true; spinWithOptions(UIViewAnimationOptions.CurveEaseIn); } } func stopSpinning() { animating = false } func spinWithOptions(options: UIViewAnimationOptions) { UIView.animateWithDuration(0.5, delay: 0.0, options: options, animations: { () -> Void in let val : CGFloat = CGFloat((M_PI / Double(2.0))); self.activityIndicatorImage.transform = CGAffineTransformRotate(self.activityIndicatorImage.transform,val) }) { (finished: Bool) -> Void in if(finished) { if(self.animating){ self.spinWithOptions(UIViewAnimationOptions.CurveLinear) } else if (options != UIViewAnimationOptions.CurveEaseOut) { self.spinWithOptions(UIViewAnimationOptions.CurveEaseOut) } } } } override func viewDidLoad() { startSpinning() } }
这是我正确的方向旋转360。
[UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionRepeat|UIViewAnimationOptionCurveLinear animations:^{ [imageIndView setTransform:CGAffineTransformRotate([imageIndView transform], M_PI-0.00001f)]; } completion:nil];
@ ram的回答是非常有帮助的。 这是一个Swift版本的答案。
private func rotateImageView() { UIView.animateWithDuration(1, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, CGFloat(M_PI_2)) }) { (finished) -> Void in if finished { self.rotateImageView() } } }
创buildanimation
- (CABasicAnimation *)spinAnimationWithDuration:(CGFloat)duration clockwise:(BOOL)clockwise repeat:(BOOL)repeats { CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; anim.toValue = clockwise ? @(M_PI * 2.0) : @(M_PI * -2.0); anim.duration = duration; anim.cumulative = YES; anim.repeatCount = repeats ? CGFLOAT_MAX : 0; return anim; }
将它添加到像这样的视图
CABasicAnimation *animation = [self spinAnimationWithDuration:1.0 clockwise:YES repeat:YES]; [self.spinningView.layer addAnimation:animation forKey:@"rotationAnimation"];
这个答案如何不同? 如果大多数函数返回对象,而不是仅仅操纵一些对象,那么你将有更清晰的代码。
对于Xamarin ios:
public static void RotateAnimation (this UIView view, float duration=1, float rotations=1, float repeat=int.MaxValue) { var rotationAnimation = CABasicAnimation.FromKeyPath ("transform.rotation.z"); rotationAnimation.To = new NSNumber (Math.PI * 2.0 /* full rotation*/ * 1 * 1); rotationAnimation.Duration = 1; rotationAnimation.Cumulative = true; rotationAnimation.RepeatCount = int.MaxValue; rotationAnimation.RemovedOnCompletion = false; view.Layer.AddAnimation (rotationAnimation, "rotationAnimation"); }
我开发了一个shiny的animation框架,可以节省您的时间语气! 使用它可以很容易地创build这个animation:
private var endlessRotater: EndlessAnimator! override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) let rotationAnimation = AdditiveRotateAnimator(M_PI).to(targetView).duration(2.0).baseAnimation(.CurveLinear) endlessRotater = EndlessAnimator(rotationAnimation) endlessRotater.animate() }
停止这个animation简单的设置为endlessRotater
。
如果你有兴趣,请看看: https : //github.com/hip4yes/Animatics
Swift:
func runSpinAnimationOnView(view:UIView , duration:Float, rotations:Double, repeatt:Float ) ->() { let rotationAnimation=CABasicAnimation(); rotationAnimation.keyPath="transform.rotation.z" let toValue = M_PI * 2.0 * rotations ; // passing it a float let someInterval = CFTimeInterval(duration) rotationAnimation.toValue=toValue; rotationAnimation.duration=someInterval; rotationAnimation.cumulative=true; rotationAnimation.repeatCount=repeatt; view.layer.addAnimation(rotationAnimation, forKey: "rotationAnimation") }
Swift 3:
var rotationAnimation = CABasicAnimation() rotationAnimation = CABasicAnimation.init(keyPath: "transform.rotation.z") rotationAnimation.toValue = NSNumber(value: (M_PI * 2.0)) rotationAnimation.duration = 2.0 rotationAnimation.isCumulative = true rotationAnimation.repeatCount = 10.0 view.layer.add(rotationAnimation, forKey: "rotationAnimation")
let val = CGFloat(M_PI_2) UIView.animate(withDuration: 1, delay: 0, options: [.repeat, .curveLinear], animations: { self.viewToRotate.transform = self.viewToRotate.transform.rotated(by: val) })
有以下不同的方式来执行UIView 360度animation。
使用CABasicAnimation
var rotationAnimation = CABasicAnimation() rotationAnimation = CABasicAnimation.init(keyPath: "transform.rotation.z") rotationAnimation.toValue = NSNumber(value: (Double.pi)) rotationAnimation.duration = 1.0 rotationAnimation.isCumulative = true rotationAnimation.repeatCount = 100.0 view.layer.add(rotationAnimation, forKey: "rotationAnimation")
这里是UIView的扩展函数,它处理启动和停止旋转操作:
extension UIView { func startRotation() { let rotation = CABasicAnimation(keyPath: "transform.rotation.z") rotation.fromValue = 0 rotation.toValue = NSNumber(value: Double.pi) rotation.duration = 1.0 rotation.isCumulative = true rotation.repeatCount = FLT_MAX self.layer.add(rotation, forKey: "rotationAnimation") } func stopRotation() { self.layer.removeAnimation(forKey: "rotationAnimation") } }
现在使用UIView.animation闭包:
UIView.animate(withDuration: 0.5, animations: { view.transform = CGAffineTransform(rotationAngle: (CGFloat(Double.pi)) }) { (isAnimationComplete) in // Animation completed }
Swift 4 ,
func rotateImage(image: UIImageView) { UIView.animate(withDuration: 1, animations: { image.transform = CGAffineTransform(rotationAngle: CGFloat.pi) image.transform = CGAffineTransform.identity }) { (completed) in self.rotateImage() } }
参考
Swift 4.0
func rotateImageView() { UIView.animate(withDuration: 0.3, delay: 0, options: .curveLinear, animations: {() -> Void in self.imageView.transform = self.imageView.transform.rotated(by: .pi / 2) }, completion: {(_ finished: Bool) -> Void in if finished { rotateImageView() } }) }
我认为你应该更好地添加一个UIVIew
分类:
#import <QuartzCore/QuartzCore.h> #import "UIView+Rotate.h"
实现UIView(旋转)
-
(void)remrotate360WithDuration:(CGFloat)duration repeatCount: (float)repeatCount { CABasicAnimation *fullRotation; fullRotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; fullRotation.fromValue = [NSNumber numberWithFloat:0]; fullRotation.toValue = [NSNumber numberWithFloat:(2*M_PI)]; // fullRotation.toValue = [NSNumber numberWithFloat:-(2*M_PI)]; // added this minus sign as i want to rotate it to anticlockwise fullRotation.duration = duration; fullRotation.speed = 2.0f; // Changed rotation speed if (repeatCount == 0) fullRotation.repeatCount = MAXFLOAT; else fullRotation.repeatCount = repeatCount; [self.layer addAnimation:fullRotation forKey:@"360"]; }
不使用这种方法:)
-
(void)remstopAllAnimations { [self.layer removeAllAnimations]; };
-
(void)rempauseAnimations { [self rempauseLayer:self.layer]; }
-
(void)remresumeAnimations { [self remresumeLayer:self.layer]; }
-
(void)rempauseLayer:(CALayer *)layer { CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; layer.speed = 0.0; layer.timeOffset = pausedTime; }
-
(void)remresumeLayer:(CALayer *)layer { CFTimeInterval pausedTime = [layer timeOffset]; layer.speed = 1.0; layer.timeOffset = 0.0; layer.beginTime = 0.0; CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; layer.beginTime = timeSincePause; }