Animation de rotation UIView Infinite à 360 degrés?

251

J'essaie de faire pivoter un UIImageView360 degrés, et j'ai regardé plusieurs tutoriels en ligne. Je ne pouvais faire en sorte qu'aucun d'entre eux ne travaille, sans UIViews'arrêter ni sauter dans une nouvelle position.

  • Comment puis-je atteindre cet objectif?

La dernière chose que j'ai essayée est:

[UIView animateWithDuration:1.0
                      delay:0.0
                    options:0
                 animations:^{
                     imageToMove.transform = CGAffineTransformMakeRotation(M_PI);
                 } 
                 completion:^(BOOL finished){
                     NSLog(@"Done!");
                 }];

Mais si j'utilise 2 * pi, il ne bouge pas du tout (puisque c'est la même position). Si j'essaye de faire juste pi (180 degrés), cela fonctionne, mais si j'appelle à nouveau la méthode, elle tourne en arrière.

MODIFIER :

[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!");
                 }];

ne fonctionne pas non plus. Il passe en 180degrés, s'arrête une fraction de seconde, puis se réinitialise en 0degrés avant de recommencer.

Derek
la source

Réponses:

334

J'ai trouvé une méthode (je l'ai un peu modifiée) qui fonctionnait parfaitement pour moi: rotation de l'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"];
}
Derek
la source
25
#import <QuartzCore / QuartzCore.h>
AlBeebe
3
ajouter QuartzCore.framework Project-> Build Phases-> Link Binaries
gjpc
9
C'est le bon code pour iOS 3.0 et inférieur, mais pour les nouveaux programmeurs et les nouveaux projets, Apple avertit désormais les utilisateurs de ces méthodes dans le code Docs & @Nate utilise les animations basées sur des blocs que Apple préfère désormais
PaulWoodIII
1
@cvsguimaraes D'après le document: "Détermine si la valeur de la propriété est la valeur à la fin du cycle de répétition précédent, plus la valeur du cycle de répétition en cours."
smad
12
Cela pourrait aider quelqu'un, [view.layer removeAllAnimations]; pour arrêter l'animation si nécessaire.
arunit21
112

Félicitations à Richard J. Ross III pour l'idée, mais j'ai trouvé que son code n'était pas tout à fait ce dont j'avais besoin. optionsJe pense que la valeur par défaut est de vous donner UIViewAnimationOptionCurveEaseInOut, ce qui ne semble pas correct dans une animation continue. De plus, j'ai ajouté une vérification afin de pouvoir arrêter mon animation à un quart de tour pair si j'en avais besoin (pas infinie , mais de durée indéfinie ), et j'ai fait accélérer l'accélération pendant les 90 premiers degrés et décélérer pendant les 90 derniers degrés (après qu'un arrêt a été demandé):

// 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;
}

Mettre à jour

J'ai ajouté la possibilité de gérer les demandes pour recommencer la rotation ( startSpin), tandis que la rotation précédente se termine (se termine). Exemple de projet ici sur Github .

Nate
la source
2
En utilisant cela, je remarque une brève pause dans l'animation à chaque angle PI / 2 (90 degrés) et une augmentation marginale de l'utilisation du processeur par rapport à la réponse choisie à l'aide de CABasicAnimation. La méthode CABasicAnimation produit une animation parfaitement fluide.
Arjun Mehta
1
@Dilip, remplacez M_PI / 2par - (M_PI / 2). (Faites défiler le code vers la droite pour voir la fin de chaque ligne)
Nate
1
@Dilip, je ne sais pas quelle durée vous utilisez dans votre code. Les mêmes 0.5secondes par 90 degrés que moi? Si c'est le cas, mon code ne vous permet pas de vous arrêter à une fraction d'une rotation de 90 degrés. Il vous permet de vous arrêter à un nombre entier d'intervalles de 90 degrés, mais ajoute une rotation supplémentaire de 90 degrés, "assouplie". Ainsi, dans le code ci-dessus, si vous appelez stopSpinaprès 0,35 seconde, il tournera pendant 0,65 seconde supplémentaire et s'arrêtera à une rotation totale de 180 degrés. Si vous avez des questions plus détaillées, vous devriez probablement ouvrir une nouvelle question. N'hésitez pas à créer un lien vers cette réponse.
Nate
1
Que voyez-vous arriver, @MohitJethwa? J'ai une application qui utilise cela, et cela fonctionne toujours pour moi sur iOS 8.1.
Nate
1
@Hemang, bien sûr, mais ce n'était pas la question. Je posterais une nouvelle question si c'est ce que vous essayez de faire.
Nate
85

Dans Swift, vous pouvez utiliser le code suivant pour une rotation infinie:

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

L'arrêt, c'est comme:

func stopRotatingView(view: UIView) {
    if view.layer.animationForKey(kRotationAnimationKey) != nil {
        view.layer.removeAnimationForKey(kRotationAnimationKey)
    }
}
Kádi
la source
Qu'est-ce que la kRotationAnimationKey?
Luda
Il s'agit d'une clé de chaîne constante, qui vous aide à identifier et à rechercher l'animation. Il peut s'agir de n'importe quelle chaîne de votre choix. Dans le processus de suppression, vous pouvez voir que l'animation est recherchée et ensuite supprimée par cette clé.
Kádi
Est-ce une chaîne ou quelque chose de spécifique?
Luda
Désolé pour la réponse tardive, mais oui, il peut s'agir de n'importe quelle chaîne.
Kádi
1
// N'importe quelle clé comme dans la réponse
Zander Zhang
67

La réponse de Nate ci-dessus est idéale pour arrêter et démarrer l'animation et donne un meilleur contrôle. J'ai été intrigué par le fait que le vôtre ne fonctionnait pas et les siens. Je voulais partager mes découvertes ici et une version plus simple du code qui animerait une UIView en continu sans caler.

Ceci est le code que j'ai utilisé,

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

J'ai utilisé «CGAffineTransformRotate» au lieu de «CGAffineTransformMakeRotation» car le premier renvoie le résultat qui est enregistré au fur et à mesure de l'animation. Cela empêchera le saut ou la réinitialisation de la vue pendant l'animation.

Une autre chose est de ne pas utiliser 'UIViewAnimationOptionRepeat' car à la fin de l'animation avant qu'elle ne commence à se répéter, elle réinitialise la transformation faisant revenir la vue à sa position d'origine. Au lieu d'une répétition, vous récursivement afin que la transformation ne soit jamais réinitialisée à la valeur d'origine car le bloc d'animation ne se termine pratiquement jamais.

Et la dernière chose est que vous devez transformer la vue par pas de 90 degrés (M_PI / 2) au lieu de 360 ​​ou 180 degrés (2 * M_PI ou M_PI). Parce que la transformation se produit comme une multiplication matricielle des valeurs sinus et cosinus.

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

Par exemple, si vous utilisez une transformation à 180 degrés, le cosinus de 180 donne -1, ce qui fait que la vue se transforme dans le sens opposé à chaque fois (la réponse de Note-Nate aura également ce problème si vous changez la valeur radian de la transformation en M_PI). Une transformation à 360 degrés demande simplement à la vue de rester où elle était, donc vous ne voyez aucune rotation du tout.

RAM
la source
L'intérêt de ma réponse (et de la réponse de Richard qui en découle) est d'utiliser des pas de 90 degrés , pour éviter de changer de direction. 90 degrés fonctionnent également relativement bien si vous souhaitez arrêter la rotation , car de nombreuses images ont une symétrie de 180 degrés ou 90 degrés.
Nate
Oui, je pensais simplement expliquer pourquoi il est utilisé :)
ram
2
@nik Mais après avoir défini le point d'arrêt là-dedans, je vois qu'il n'y aurait pas de débordement de pile car le bloc d'achèvement est appelé par le système, à ce moment-là, il rotateImageViewest déjà terminé. Ce n'est donc pas vraiment récursif dans ce sens.
huggie
1
Bonne réponse +1 pour une petite version de code avec la fonctionnalité souhaitée.
Pradeep Mittal
1
J'ai vraiment apprécié l'explication de pourquoi ne pas utiliser UIViewAnimationOptionRepeat.
Jonathan Zhan
21

Si tout ce que vous voulez faire est de faire pivoter l'image à l'infini, cela fonctionne très bien et est très 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];

D'après mon expérience, cela fonctionne parfaitement, mais assurez-vous que votre image est capable de pivoter autour de son centre sans aucun décalage, ou l'animation d'image "sautera" une fois qu'elle arrivera à PI.

Pour changer la direction de la rotation, changez le signe de angle( angle *= -1).

Mettre à jour les commentaires de @AlexPretzlav m'a fait revisiter cela, et je me suis rendu compte que lorsque j'ai écrit cela, l'image que je tournais était reflétée le long de l'axe vertical et horizontal, ce qui signifie que l'image ne tournait que de 90 degrés puis se réinitialisait, même si elle ressemblait à il continuait de tourner tout autour.

Donc, si votre image est comme la mienne, cela fonctionnera très bien, cependant, si l'image n'est pas symétrique, vous remarquerez le "snap" à son orientation d'origine après 90 degrés.

Pour faire pivoter une image non symétrique, il vaut mieux utiliser la réponse acceptée.

L'une de ces solutions moins élégantes, présentée ci-dessous, fera vraiment pivoter l'image, mais il peut y avoir un bégaiement notable au redémarrage de l'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];
    }];
}

Vous pouvez également le faire uniquement avec des blocs, comme le suggère @ richard-j-ross-iii, mais vous obtiendrez un avertissement de boucle de rétention car le bloc se capture lui-même:

__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();
levigroker
la source
1
Pour moi, cela n'a tourné que d'un quart de tour.
Alex Pretzlav
@AlexPretzlav Assurez-vous que vous avez UIViewAnimationOptionRepeatdéfini votre animation options.
levigroker
1
Je l'ai fait, il tourne à 45 °, puis boucle en sautant à 0 ° et en tournant à nouveau à 45 °
Alex Pretzlav
Mis à jour ma réponse avec de nouvelles informations sur les raisons pour lesquelles cela "a fonctionné"
levigroker
21

Ma contribution avec une extension Swift de la solution vérifiée:

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

Obsolète:

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")
    }
}
Kevin ABRIOUX
la source
Pour le nombre de répétitions, on peut utiliser .infinity.
M. Rogers
15

La réponse impressionnante de David Rysanek mise à jour vers 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")

        }   

    }
}
Geoff H
la source
a très bien fonctionné pour moi (sur un UIButton). Je vous remercie!
agrippa
j'ai aimé ta simplicité
Nicolas Manzini
10

Utilisez le quart de tour et augmentez le tour progressivement.

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);
Richard J. Ross III
la source
Je suis très nouveau dans les blocs, mais cette méthode lance l'erreur "Types de pointeurs de bloc incompatibles envoyant 'void (^ const__strong ()' au paramètre de type 'void (^) (BOOL)'. J'ai essayé de changer ma méthode de rotation dans le code dans ma question à l'imageToMove.transform = CGAffineTransformRotate (imageToMove.transform, M_PI / 2); vous avez utilisé, et l'animation a encore un léger retard, et se réinitialise avant le prochain tour.
Derek
10

Cela fonctionnait pour moi:

[UIView animateWithDuration:1.0
                animations:^
{
    self.imageView.transform = CGAffineTransformMakeRotation(M_PI);
    self.imageView.transform = CGAffineTransformMakeRotation(0);
}];
inigo333
la source
Cela n'arrive pas pendant des temps infinis!
byJeevan
9

J'ai trouvé du bon code dans ce dépôt ,

Voici le code à partir duquel j'ai fait de petits changements en fonction de mon besoin de vitesse :)

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
Dilip
la source
@BreadicalMD, les mathématiques sont comme la programmation, vous pouvez faire la même chose de plusieurs façons. Cela ne signifie pas qu'un seul moyen est correct et que d'autres ne le sont pas. Oui, il peut y avoir des avantages et des inconvénients dans tous les sens, mais cela ne signifie pas que vous pouvez vous interroger sur la méthode de programmation de quelqu'un, vous pouvez suggérer des inconvénients pour cette méthode et des avantages pour votre méthode. Ce commentaire est vraiment offensant et vous ne devez pas ajouter de commentaire comme celui-ci.
Dilip
1
Je suis désolé que cela vous ait offensé Dilip, mais écrire 2 * M_PI est objectivement plus clair que ((360 * M_PI) / 180), et écrire ce dernier démontre un manque de compréhension du sujet traité.
BreadicalMD
1
@BreadicalMD Pas de soucis, mais c'est une bonne suggestion, j'ai mis à jour mon code. Merci.
Dilip
9

Voici ma solution rapide en tant qu'extension UIView. Il pourrait être considéré comme une simulation d'un comportement UIActivityIndicator sur n'importe quel UIImageView.

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

}
David Rysanek
la source
9

Une version 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")
    }
}

Et rappelez - vous d'appeler startRotateà viewWillAppearpas viewDidLoad.

Victor Choy
la source
3

Vous pouvez également faire le même type d'animation en utilisant UIView et des blocs. Voici une méthode d'extension de classe qui peut faire pivoter la vue de n'importe quel angle.

- (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();
    }
}
MiKL
la source
Cette méthode (appel récursif à la fin) fonctionnerait-elle bien avec une animation à évolution rapide? Par exemple, la durée serait d'env. 1 / 30s pour pouvoir modifier la vitesse de rotation de l'objet en temps réel, réagir par exemple aux touches?
alecail
3

Si quelqu'un voulait la solution de Nates mais en rapide, voici une traduction rapide et approximative:

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()
    }
}
Adrian_H
la source
3

pour 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");
}
Danil Shaykhutdinov
la source
2

C'est ainsi que je fais pivoter 360 ° dans la bonne direction.

[UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionRepeat|UIViewAnimationOptionCurveLinear
                     animations:^{
                         [imageIndView setTransform:CGAffineTransformRotate([imageIndView transform], M_PI-0.00001f)];
                     } completion:nil];
Pavel
la source
2

Créer l'animation

- (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;
}

Ajoutez-le à une vue comme celle-ci

CABasicAnimation *animation = [self spinAnimationWithDuration:1.0 clockwise:YES repeat:YES];
[self.spinningView.layer addAnimation:animation forKey:@"rotationAnimation"];

En quoi cette réponse est-elle différente? Vous aurez un code beaucoup plus propre si la plupart de vos fonctions retournent des objets au lieu de simplement manipuler certains objets ici et là.

hfossli
la source
2

Il existe différentes façons d'effectuer une animation à 360 degrés avec UIView.

Utilisation de 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")


Voici une fonction d'extension pour UIView qui gère les opérations de rotation de démarrage et d'arrêt:

extension UIView {

    // Start rotation
    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")
    }

    // Stop rotation
    func stopRotation() {
        self.layer.removeAnimation(forKey: "rotationAnimation")
    }
}


Maintenant en utilisant, fermeture UIView.animation :

UIView.animate(withDuration: 0.5, animations: { 
      view.transform = CGAffineTransform(rotationAngle: (CGFloat(Double.pi)) 
}) { (isAnimationComplete) in
    // Animation completed 
}
Krunal
la source
2

La réponse de @ ram a été très utile. Voici une version Swift de la réponse.

Swift 2

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()
            }
    }
}

Swift 3,4,5

private func rotateImageView() {

    UIView.animate(withDuration: 1, delay: 0, options: UIView.AnimationOptions.curveLinear, animations: { () -> Void in
        self.imageView.transform = self.imageView.transform.rotated(by: .pi / 2)
    }) { (finished) -> Void in
        if finished {
            self.rotateImageView()
        }
    }
}
yoninja
la source
comment puis-je arrêter la rotation?
user2526811
1
Et si vous mettiez un drapeau? Donc, peut-être avez-vous une fonction pour arrêter l'animation: var stop = false private func stopRotation() { stop = true } Ensuite, à l'intérieur if finished {...}:if finished { if stop { return} self.rotateImageView() }
yoninja
Merci, j'ai fait exactement la même chose. Merci pour votre réponse.
user2526811
1

J'ai développé un cadre d'animation brillant qui peut vous faire gagner du temps! En l'utilisant, cette animation peut être créée très facilement:

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()
}

pour arrêter cette animation, définissez simplement nilsur endlessRotater.

Si vous êtes intéressé, jetez un œil: https://github.com/hip4yes/Animatics

Nikita Arkhipov
la source
1

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()
        }
    }

Réf.

Mohammad Zaid Pathan
la source
1

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()
        }
    })
}
Abhishek Jain
la source
1

Extension Swift 5 UIView à l'aide d'animations d'images clés

Cette approche nous permet d'utiliser directement UIView.AnimationOptions.repeat

public extension UIView {

    func animateRotation(duration: TimeInterval, repeat: Bool, completion: ((Bool) -> ())?) {

        var options = UIView.KeyframeAnimationOptions(rawValue: UIView.AnimationOptions.curveLinear.rawValue)

        if `repeat` {
            options.insert(.repeat)
        }

        UIView.animateKeyframes(withDuration: duration, delay: 0, options: options, animations: {

            UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.25, animations: {
                self.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2)
            })

            UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.25, animations: {
                self.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
            })

            UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.25, animations: {
                self.transform = CGAffineTransform(rotationAngle: 3*CGFloat.pi/2)
            })

            UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25, animations: {
                self.transform = CGAffineTransform(rotationAngle: 2*CGFloat.pi)
            })

        }, completion: completion)

    }

}
Brody Robertson
la source
2
J'aime le vôtre, assez propre aussi
Nicolas Manzini
0

Rapide :

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


    }
Erhan Demirci
la source
0

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")
Deepak Carpenter
la source
0
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)
})
Warif Akhand Rishi
la source
-1

Je pense que vous devriez mieux ajouter une UIVIewcatégorie:

#import <QuartzCore/QuartzCore.h>
#import "UIView+Rotate.h"

Implémentation UIView (Rotation)

  • (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"];
    }

Ne pas utiliser ces méthodes :)

  • (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;
    }
Zgpeace
la source
Bien joué. Je veux juste commenter la catégorie UIImageView. Merci tout de même.
Zgpeace