UIView Бесконечная анимация вращения на 360 градусов? - PullRequest
239 голосов
/ 23 марта 2012

Я пытаюсь повернуть UIImageView 360 градусов, и посмотрел несколько онлайн-уроков. Я не мог заставить никого из них работать, без остановки или перехода на новую позицию UIView.

  • Как мне этого добиться?

Последнее, что я попробовал, это:

* * 1010

Но если я использую 2 * пи, он вообще не двигается (поскольку это одна и та же позиция). Если я попытаюсь сделать только пи (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!");
                 }];

тоже не работает. Он достигает 180 градусов, останавливается на доли секунды, затем сбрасывается обратно до 0 градусов, прежде чем снова запустится.

Ответы [ 26 ]

331 голосов
/ 23 марта 2012

Нашел метод (я его немного изменил), который отлично сработал для меня: 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"];
}
110 голосов
/ 28 июня 2012

Спасибо Ричарду Дж. Россу 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;
}

Обновление

Я добавил возможность обрабатывать запросы, чтобы снова начать вращаться (startSpin), пока предыдущий вращение завершается (завершается). Пример проекта здесь, на Github .

70 голосов
/ 25 февраля 2015

В Swift вы можете использовать следующий код для бесконечного вращения:

Swift 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

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)
    }
}
65 голосов
/ 19 июля 2013

Ответ Нейта выше идеален для остановки и запуска анимации и дает лучший контроль. Я был заинтригован, почему ваш не работает, а он работает. Я хотел бы поделиться здесь своими выводами и более простой версией кода, который бы непрерывно анимировал UIView без остановки.

Это код, который я использовал,

- (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», потому что первый возвращает результат, который сохраняется в процессе анимации. Это предотвратит скачок или сброс вида во время анимации.

Другое дело не использовать UIViewAnimationOptionRepeat, потому что в конце анимации, прежде чем он начинает повторяться, он сбрасывает преобразование, заставляя представление вернуться в исходное положение. Вместо повтора вы выполняете рекурсию, чтобы преобразование никогда не сбрасывалось до исходного значения, поскольку блок анимации практически никогда не заканчивается.

И последнее, вы должны преобразовать вид с шагом 90 градусов (M_PI / 2) вместо 360 или 180 градусов (2 * M_PI или M_PI). Потому что преобразование происходит как матричное умножение значений синуса и косинуса. * * 1010

t' =  [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] * t

Так, скажем, если вы используете 180-градусное преобразование, косинус 180 дает -1, заставляя представление трансформироваться в противоположном направлении каждый раз (ответ Нота-Нейта также будет иметь эту проблему, если вы измените значение радиана преобразования на M_PI ). 360-градусное преобразование просто просит вид остаться там, где он был, следовательно, вы вообще не видите никакого поворота.

21 голосов
/ 25 февраля 2013

Если все, что вы хотите сделать, это вращать изображение бесконечно, это работает довольно хорошо и очень просто:

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];

По моему опыту, это работает безупречно, но убедитесь, что ваше изображение способно вращаться вокруг его центра без каких-либо смещений, иначе анимация изображения будет "прыгать", как только она перейдет к PI.

Чтобы изменить направление вращения, измените знак angle (angle *= -1).

Обновление Комментарии @AlexPretzlav заставили меня вернуться к этому, и я понял, что когда я писал это, изображение, которое я вращал, отражалось вдоль вертикальной и горизонтальной оси, то есть изображение действительно вращалось только на 90 °. градусов, а затем сбрасывается, хотя выглядело как будто оно продолжало вращаться вокруг.

Итак, если ваше изображение похоже на мое, это будет работать отлично, однако, если изображение не симметрично, вы заметите «защелку» к исходной ориентации после 90 градусов.

Чтобы повернуть несимметричное изображение, лучше принять принятый ответ.

Одно из этих менее изящных решений, показанных ниже, действительно повернет изображение, но при перезапуске анимации может возникнуть заметное заикание:

- (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, но вы получите предупреждение о сохранении цикла, поскольку блок захватывает сам себя:

__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();
20 голосов
/ 10 декабря 2015

Мой вклад с расширением Swift из проверенного решения:

Swift 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")
    }
}

устарело:

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")
    }
}
10 голосов
/ 29 февраля 2016

Это работало для меня:

[UIView animateWithDuration:1.0
                animations:^
{
    self.imageView.transform = CGAffineTransformMakeRotation(M_PI);
    self.imageView.transform = CGAffineTransformMakeRotation(0);
}];
10 голосов
/ 23 марта 2012

Используйте четверть оборота и увеличивайте его постепенно.

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);
9 голосов
/ 06 октября 2014

Я нашел хороший код в этом хранилище ,

Вот код, из которого я сделал небольшие изменения в соответствии с моей потребностью в скорости:)

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
9 голосов
/ 18 апреля 2018

Удивительный ответ Дэвида Рисанека обновлен до 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")

        }   

    }
}
...