ios UIView 无限 360 度旋转动画?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/9844925/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
UIView Infinite 360 degree rotation animation?
提问by Derek
I'm trying to rotate a UIImageView
360 degrees, and have looked at several tutorials online. I could get none of them working, without the UIView
either stopping, or jumping to a new position.
我正在尝试旋转UIImageView
360 度,并在线查看了几个教程。我不能让他们工作,UIView
要么停下来,要么跳到一个新的位置。
- How can I achieve this?
- 我怎样才能做到这一点?
The latest thing I've tried is:
我尝试过的最新一件事是:
[UIView animateWithDuration:1.0
delay:0.0
options:0
animations:^{
imageToMove.transform = CGAffineTransformMakeRotation(M_PI);
}
completion:^(BOOL finished){
NSLog(@"Done!");
}];
But if I use 2*pi, it doesn't move at all (since it's the same position). If I try to do just pi (180 degrees), it works, but if I call the method again, it rotates backwards.
但是如果我使用 2*pi,它根本不会移动(因为它的位置相同)。如果我尝试只做 pi(180 度),它会起作用,但如果我再次调用该方法,它会向后旋转。
EDIT:
编辑:
[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!");
}];
doesn't work either. It goes to 180
degrees, pauses for a split second, then resets back to 0
degrees before it starts again.
也不起作用。它转到180
度数,暂停一秒钟,然后0
在再次开始之前重置回度数。
回答by Derek
Found a method (I modified it a bit) that worked perfectly for me: iphone UIImageView rotation
找到了一个非常适合我的方法(我稍微修改了一下):iphone UIImageView rotation
#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"];
}
回答by Nate
Kudos to Richard J. Ross III for the idea, but I found that his code wasn't quite what I needed. The default for options
, I believe, is to give you UIViewAnimationOptionCurveEaseInOut
, which doesn't look right in a continuous animation. Also, I added a check so that I could stop my animation at an even quarter turn if I needed (not infinite, but of indefiniteduration), and made the acceleration ramp up during the first 90 degrees, and decelerate during the last 90 degrees (after a stop has been requested):
感谢 Richard J. Ross III 提出的想法,但我发现他的代码并不是我所需要的。options
我相信的默认值是给你UIViewAnimationOptionCurveEaseInOut
,这在连续动画中看起来不正确。此外,我添加了一个检查,以便我可以在需要时在偶数四分之一圈停止动画(不是无限,而是无限期),并在前 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;
}
Update
更新
I added the ability to handle requests to start spinning again (startSpin
), while the previous spin is winding down (completing). Sample project here on Github.
我添加了处理请求以再次开始旋转 ( startSpin
) 的功能,而之前的旋转正在结束(完成)。Github 上的示例项目。
回答by Kádi
In Swift, you can use the following code for infinite rotation:
在 Swift 中,您可以使用以下代码进行无限旋转:
Swift 4
斯威夫特 4
extension UIView {
private static let kRotationAnimationKey = "rotationanimationkey"
func rotate(duration: Double = 1) {
if layer.animation(forKey: UIView.kRotationAnimationKey) == nil {
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotationAnimation.fromValue = 0.0
rotationAnimation.toValue = Float.pi * 2.0
rotationAnimation.duration = duration
rotationAnimation.repeatCount = Float.infinity
layer.add(rotationAnimation, forKey: UIView.kRotationAnimationKey)
}
}
func stopRotating() {
if layer.animation(forKey: UIView.kRotationAnimationKey) != nil {
layer.removeAnimation(forKey: UIView.kRotationAnimationKey)
}
}
}
Swift 3
斯威夫特 3
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)
}
}
Stopping is like:
停止就像:
func stopRotatingView(view: UIView) {
if view.layer.animationForKey(kRotationAnimationKey) != nil {
view.layer.removeAnimationForKey(kRotationAnimationKey)
}
}
回答by ram
Nate's answer above is ideal for stop and start animation and gives a better control. I was intrigued why yours didn't work and his does. I wanted to share my findings here and a simpler version of the code that would animate a UIView continuously without stalling.
上面 Nate 的回答非常适合停止和开始动画,并提供更好的控制。我很好奇为什么你的不起作用而他的起作用。我想在这里分享我的发现和一个更简单的代码版本,它可以连续地为 UIView 设置动画而不会停顿。
This is the code I used,
这是我使用的代码,
- (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];
}
}];
}
I used 'CGAffineTransformRotate' instead of 'CGAffineTransformMakeRotation' because the former returns the result which is saved as the animation proceeds. This will prevent the jumping or resetting of the view during the animation.
我使用了 'CGAffineTransformRotate' 而不是 'CGAffineTransformMakeRotation',因为前者返回的结果随着动画的进行而被保存。这将防止在动画期间跳转或重置视图。
Another thing is not to use 'UIViewAnimationOptionRepeat' because at the end of the animation before it starts repeating, it resets the transform making the view jump back to its original position. Instead of a repeat, you recurse so that the transform is never reset to the original value because the animation block virtually never ends.
另一件事是不要使用“UIViewAnimationOptionRepeat”,因为在动画开始重复之前,它会在动画结束时重置变换,使视图跳回其原始位置。不是重复,而是递归,以便变换永远不会重置为原始值,因为动画块几乎永远不会结束。
And the last thing is, you have to transform the view in steps of 90 degrees (M_PI / 2) instead of 360 or 180 degrees (2*M_PI or M_PI). Because transformation occurs as a matrix multiplication of sine and cosine values.
最后一件事是,您必须以 90 度 (M_PI / 2) 而不是 360 或 180 度(2*M_PI 或 M_PI)的步长转换视图。因为变换是作为正弦值和余弦值的矩阵乘法发生的。
t' = [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] * t
So, say if you use 180-degree transformation, the cosine of 180 yields -1 making the view transform in opposite direction each time (Note-Nate's answer will also have this issue if you change the radian value of transformation to M_PI). A 360-degree transformation is simply asking the view to remain where it was, hence you don't see any rotation at all.
因此,假设您使用 180 度变换,则 180 的余弦产生 -1,每次都使视图沿相反方向进行变换(如果将变换的弧度值更改为 M_PI,Note-Nate 的回答也会出现此问题)。360 度变换只是要求视图保持原样,因此您根本看不到任何旋转。
回答by levigroker
If all you want to do is rotate the image endlessly, this works quite well, and is very simple:
如果你想做的只是无休止地旋转图像,这很有效,而且非常简单:
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];
In my experience, this works flawlessly, but be sure your image is capable of being rotated around its center without any offsets, or the image animation will "jump" once it makes it around to PI.
根据我的经验,这可以完美地工作,但请确保您的图像能够围绕其中心旋转而没有任何偏移,否则图像动画一旦转到 PI 就会“跳跃”。
To change the direction of the spin, change the sign of angle
(angle *= -1
).
要改变旋转的方向,改变angle
( angle *= -1
)的符号。
UpdateComments by @AlexPretzlav made me revisit this, and I realized that when I wrote this the image I was rotating was mirrored along both the vertical and horizontal axis, meaning the image was indeed only rotating 90 degrees and then resetting, though it lookedlike it was continuing to rotate all the way around.
@AlexPretzlav 的更新评论让我重新审视了这个,我意识到当我写这篇文章时,我旋转的图像是沿垂直和水平轴镜像的,这意味着图像确实只旋转了 90 度然后重新设置,虽然它看起来像它一直在不停地旋转。
So, if your image is like mine was, this will work great, however, if the image is not symmetrical, you'll notice the "snap" back to the original orientation after 90 degrees.
所以,如果你的图像和我的一样,这会很好用,但是,如果图像不对称,你会注意到在 90 度后“快速”回到原始方向。
To rotate a non-symmetrical image, you're better off with the accepted answer.
要旋转非对称图像,最好使用已接受的答案。
One of these less elegant solutions, seen below, will truly rotate the image, but there may be a noticeable stutter when the animation is restarted:
如下所示,这些不太优雅的解决方案之一将真正旋转图像,但在重新启动动画时可能会出现明显的卡顿:
- (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];
}];
}
You could also do this just with blocks, as @richard-j-ross-iii suggests, but you will get a retain loop warning since the block is capturing itself:
正如@richard-j-ross-iii 所建议的那样,您也可以仅使用块来执行此操作,但是由于块正在捕获自身,因此您将收到保留循环警告:
__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();
回答by Kevin ABRIOUX
My contribution with a Swift Extension from the checked solution :
我对来自已检查解决方案的 Swift 扩展的贡献:
Swift 4.0
斯威夫特 4.0
extension UIView{
func rotate() {
let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.toValue = NSNumber(value: Double.pi * 2)
rotation.duration = 1
rotation.isCumulative = true
rotation.repeatCount = Float.greatestFiniteMagnitude
self.layer.add(rotation, forKey: "rotationAnimation")
}
}
Deprecated :
已弃用:
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")
}
}
回答by Geoff H
David Rysanek's awesome answer updated to Swift 4:
David Rysanek 的精彩回答更新到Swift 4:
import UIKit
extension UIView {
func startRotating(duration: CFTimeInterval = 3, repeatCount: Float = Float.infinity, clockwise: Bool = true) {
if self.layer.animation(forKey: "transform.rotation.z") != nil {
return
}
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
let direction = clockwise ? 1.0 : -1.0
animation.toValue = NSNumber(value: .pi * 2 * direction)
animation.duration = duration
animation.isCumulative = true
animation.repeatCount = repeatCount
self.layer.add(animation, forKey:"transform.rotation.z")
}
func stopRotating() {
self.layer.removeAnimation(forKey: "transform.rotation.z")
}
}
}
回答by inigo333
This was working for me:
这对我有用:
[UIView animateWithDuration:1.0
animations:^
{
self.imageView.transform = CGAffineTransformMakeRotation(M_PI);
self.imageView.transform = CGAffineTransformMakeRotation(0);
}];
回答by Richard J. Ross III
Use quarter turn, and increase the turn incrementally.
使用四分之一圈,并逐渐增加圈数。
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);
回答by Dilip
I have found nice code in this repository,
我在这个存储库中找到了不错的代码,
Here is the code from it i have done small changes according to my need for speed :)
这是它的代码,我根据我对速度的需要做了一些小的改动:)
UIImageView+Rotate.h
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
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