UIView Infinite 360-Grad-Rotationsanimation?


251

Ich versuche, mich um UIImageView360 Grad zu drehen , und habe mir online mehrere Tutorials angesehen. Ich konnte keinen von ihnen zum Laufen bringen, ohne UIViewanzuhalten oder in eine neue Position zu springen.

  • Wie kann ich das erreichen?

Das Letzte, was ich versucht habe, ist:

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

Aber wenn ich 2 * pi benutze, bewegt es sich überhaupt nicht (da es die gleiche Position ist). Wenn ich versuche, nur pi (180 Grad) zu machen, funktioniert es, aber wenn ich die Methode erneut aufrufe, dreht sie sich rückwärts.

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

funktioniert auch nicht. Es geht zu 180Grad, pausiert für den Bruchteil einer Sekunde und wird dann auf 0Grad zurückgesetzt, bevor es erneut beginnt.

Antworten:


334

Ich habe eine Methode gefunden (ich habe sie ein wenig modifiziert), die perfekt für mich funktioniert hat: 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"];
}

25
#import <QuartzCore / QuartzCore.h>
AlBeebe

3
füge QuartzCore.framework Project-> Build Phases-> Link Binaries hinzu
gjpc

9
Dies ist der richtige Code für iOS 3.0 und niedriger, aber für neuere Programmierer und neue Projekte warnt Apple Benutzer jetzt vor diesen Methoden. Der Docs & @Nate-Code verwendet die blockbasierten Animationen, die Apple jetzt bevorzugt
PaulWoodIII

1
@cvsguimaraes Aus dem Dokument: "Bestimmt, ob der Wert der Eigenschaft der Wert am Ende des vorherigen Wiederholungszyklus plus dem Wert des aktuellen Wiederholungszyklus ist."
smad

12
Dies könnte jemandem helfen, [view.layer removeAllAnimations]; um die Animation bei Bedarf zu stoppen.
Arunit21

112

Ein großes Lob an Richard J. Ross III für die Idee, aber ich fand, dass sein Code nicht ganz das war, was ich brauchte. optionsIch glaube, die Standardeinstellung für ist, Ihnen zu geben UIViewAnimationOptionCurveEaseInOut, was in einer fortlaufenden Animation nicht richtig aussieht. Außerdem habe ich eine Überprüfung hinzugefügt, damit ich meine Animation bei Bedarf um eine gleichmäßige Vierteldrehung stoppen kann (nicht unendlich , aber von unbestimmter Dauer), und die Beschleunigung in den ersten 90 Grad ansteigen und in den letzten 90 Grad verlangsamen lassen (nachdem ein Stopp angefordert wurde):

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

Aktualisieren

Ich habe die Möglichkeit hinzugefügt, Anforderungen zu verarbeiten, um erneut mit dem Drehen zu beginnen ( startSpin), während der vorherige Dreh beendet wird (abgeschlossen). Beispielprojekt hier auf Github .


2
Auf diese Weise bemerke ich eine kurze Pause in der Animation bei jedem PI / 2-Winkel (90 Grad) und einen geringfügigen Anstieg der CPU-Auslastung gegenüber der ausgewählten Antwort mit CABasicAnimation. Die CABasicAnimation-Methode erzeugt eine fehlerfrei glatte Animation.
Arjun Mehta

1
@ Dilip, ersetzen M_PI / 2durch - (M_PI / 2). (Scrollen Sie den Code nach rechts, um das Ende jeder Zeile zu sehen.)
Nate

1
@ Dilip, ich weiß nicht, welche Dauer Sie in Ihrem Code verwenden. Die gleichen 0.5Sekunden pro 90 Grad wie ich? Wenn ja, können Sie mit meinem Code nicht bei einem Bruchteil einer 90-Grad-Drehung anhalten . Sie können in einer ganzen Reihe von 90-Grad-Intervallen anhalten, aber eine zusätzliche 90-Grad-Drehung hinzufügen, die "erleichtert" wird. Wenn Sie im obigen Code stopSpinnach 0,35 Sekunden aufrufen , dreht es sich für weitere 0,65 Sekunden und stoppt bei einer Gesamtdrehung von 180 Grad. Wenn Sie detailliertere Fragen haben, sollten Sie wahrscheinlich eine neue Frage öffnen. Fühlen Sie sich frei, auf diese Antwort zu verlinken.
Nate

1
Was sehen Sie, @MohitJethwa? Ich habe eine App, die dies verwendet, und sie funktioniert immer noch für mich unter iOS 8.1.
Nate

1
@Hemang, klar, aber das war diese Frage nicht. Ich würde eine neue Frage stellen, wenn Sie dies versuchen.
Nate

85

In Swift können Sie den folgenden Code für die unendliche Rotation verwenden:

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

Anhalten ist wie:

func stopRotatingView(view: UIView) {
    if view.layer.animationForKey(kRotationAnimationKey) != nil {
        view.layer.removeAnimationForKey(kRotationAnimationKey)
    }
}

Was ist der kRotationAnimationKey?
Luda

Es ist ein konstanter String-Schlüssel, mit dem Sie die Animation identifizieren und durchsuchen können. Es kann eine beliebige Zeichenfolge sein. Während des Entfernungsprozesses können Sie sehen, dass die Animation mit diesem Schlüssel durchsucht und dann gelöscht wird.
Kádi

Ist es eine Zeichenfolge oder etwas Bestimmtes?
Luda

Entschuldigung für die späte Antwort, aber ja, es kann eine beliebige Zeichenfolge sein.
Kádi

1
// Beliebiger Schlüssel wie in der Antwort
Zander Zhang

67

Die obige Antwort von Nate ist ideal zum Stoppen und Starten von Animationen und bietet eine bessere Kontrolle. Ich war fasziniert, warum deine nicht funktioniert hat und seine. Ich wollte meine Erkenntnisse hier und eine einfachere Version des Codes teilen, die eine UIView kontinuierlich animieren würde, ohne zu blockieren.

Dies ist der Code, den ich verwendet habe,

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

Ich habe 'CGAffineTransformRotate' anstelle von 'CGAffineTransformMakeRotation' verwendet, da erstere das Ergebnis zurückgibt, das im Verlauf der Animation gespeichert wird. Dies verhindert das Springen oder Zurücksetzen der Ansicht während der Animation.

Eine andere Sache ist, 'UIViewAnimationOptionRepeat' nicht zu verwenden, da am Ende der Animation, bevor sie sich zu wiederholen beginnt, die Transformation zurückgesetzt wird, wodurch die Ansicht an ihre ursprüngliche Position zurückspringt. Anstelle einer Wiederholung rekursieren Sie, sodass die Transformation niemals auf den ursprünglichen Wert zurückgesetzt wird, da der Animationsblock praktisch nie endet.

Und das Letzte ist, dass Sie die Ansicht in Schritten von 90 Grad (M_PI / 2) anstelle von 360 oder 180 Grad (2 * M_PI oder M_PI) transformieren müssen. Weil die Transformation als Matrixmultiplikation von Sinus- und Cosinuswerten erfolgt.

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

Wenn Sie also eine 180-Grad-Transformation verwenden, ergibt der Cosinus von 180 -1, wodurch die Ansicht jedes Mal in die entgegengesetzte Richtung transformiert wird (die Antwort von Note-Nate hat auch dieses Problem, wenn Sie den Bogenmaßwert der Transformation in M_PI ändern). Bei einer 360-Grad-Transformation wird lediglich die Ansicht aufgefordert, dort zu bleiben, wo sie war. Daher sehen Sie überhaupt keine Drehung.


Der springende Punkt meiner Antwort (und Richards Antwort, von der sie abgeleitet ist) besteht darin, 90-Grad- Schritte zu verwenden, um einen Richtungswechsel zu vermeiden. 90 Grad funktionieren auch relativ gut, wenn Sie die Drehung jemals stoppen möchten , da viele Bilder entweder eine 180-Grad- oder eine 90-Grad-Symmetrie aufweisen.
Nate

Ja, ich dachte nur, ich würde erklären, warum es verwendet wird :)
RAM

2
@nik Aber nachdem ich dort den Haltepunkt gesetzt habe, sehe ich, dass es keinen Stapelüberlauf geben würde, da der Abschlussblock vom System aufgerufen wird und zu diesem Zeitpunkt rotateImageViewbereits fertig ist. In diesem Sinne ist es also nicht wirklich rekursiv.
Huggie

1
Gute Antwort +1 für eine kleine Version des Codes mit der gewünschten Funktionalität.
Pradeep Mittal

1
Ich habe die Erklärung, warum UIViewAnimationOptionRepeat nicht verwendet wird, sehr geschätzt.
Jonathan Zhan

21

Wenn Sie das Bild nur endlos drehen möchten, funktioniert dies recht gut und ist sehr einfach:

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

Nach meiner Erfahrung funktioniert dies einwandfrei, aber stellen Sie sicher, dass Ihr Bild ohne Offsets um seine Mitte gedreht werden kann, da die Bildanimation sonst "springt", sobald es zu PI gelangt.

Um die Drehrichtung zu ändern, ändern Sie das Vorzeichen von angle( angle *= -1).

Update Kommentare von @AlexPretzlav hat mich dies erneut, und ich erkannte , dass , wenn ich schrieb dies das Bild , das ich wurde sowohl gespiegelt rotierte entlang der vertikalen und horizontalen Achse wurde das Bild bedeutet in der Tat nur Drehen um 90 Grad und Zurücksetzen dann, wenn es aussah wie es drehte sich weiter umher.

Wenn Ihr Bild so ist wie meins, funktioniert dies hervorragend. Wenn das Bild jedoch nicht symmetrisch ist, werden Sie feststellen, dass nach 90 Grad wieder in die ursprüngliche Ausrichtung zurückgekehrt ist.

Um ein nicht symmetrisches Bild zu drehen, ist die akzeptierte Antwort besser geeignet.

Eine dieser weniger eleganten Lösungen (siehe unten) dreht das Bild wirklich, aber beim Neustart der Animation kann es zu einem merklichen Ruckeln kommen:

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

Sie können dies auch nur mit Blöcken tun, wie @ richard-j-ross-iii vorschlägt, aber Sie erhalten eine Retain-Loop-Warnung, da der Block sich selbst erfasst:

__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();

1
Dies drehte sich für mich nur um eine viertel Umdrehung.
Alex Pretzlav

@AlexPretzlav Stellen Sie sicher, dass Sie UIViewAnimationOptionRepeatIhre Animation eingestellt haben options.
Levigroker

1
Ich habe es getan, es dreht sich um 45 ° und schleift dann, indem es auf 0 ° zurückspringt und wieder um 45 ° dreht
Alex Pretzlav

Aktualisierte meine Antwort mit neuen Informationen darüber, warum dies "funktionierte"
Levigroker

21

Mein Beitrag mit einer Swift-Erweiterung aus der geprüften Lösung:

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

Veraltet:

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

Für die Wiederholungszählung kann man verwenden .infinity.
Herr Rogers

15

David Rysaneks großartige Antwort wurde auf Swift 4 aktualisiert :

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

        }   

    }
}

hat sehr gut für mich funktioniert (auf einem UIButton). Danke dir!
Agrippa

Ich mochte Ihre Einfachheit
Nicolas Manzini

10

Verwenden Sie eine Vierteldrehung und erhöhen Sie die Drehung schrittweise.

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

Ich bin sehr neu in Blöcken, aber diese Methode löst den Fehler "Inkompatible Blockzeigertypen, die 'void (^ const__strong ()' an den Parameter vom Typ 'void (^) (BOOL)' senden. Ich habe versucht, meine Rotationsmethode im Code zu ändern In meiner Frage an imageToMove.transform = CGAffineTransformRotate (imageToMove.transform, M_PI / 2) haben Sie verwendet, und die Animation hat immer noch eine leichte Verzögerung und wird vor der nächsten Runde zurückgesetzt.
Derek

10

Das hat bei mir funktioniert:

[UIView animateWithDuration:1.0
                animations:^
{
    self.imageView.transform = CGAffineTransformMakeRotation(M_PI);
    self.imageView.transform = CGAffineTransformMakeRotation(0);
}];

Es passiert nicht für unendliche Zeiten!
Jeevan

9

Ich habe schönen Code in diesem Repository gefunden ,

Hier ist der Code davon Ich habe kleine Änderungen vorgenommen, je nach meinem Bedürfnis nach Geschwindigkeit :)

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

@BreadicalMD, Mathe ist wie Programmieren. Sie können dasselbe auf mehrere Arten tun. Das bedeutet nicht, dass nur ein Weg korrekt ist und andere nicht. Ja, es kann für jeden Weg einige Vor- und Nachteile geben. Dies bedeutet jedoch nicht, dass Sie Fragen zu einer Programmiermethode stellen können. Sie können Nachteile für diese Methode und Vor- und Nachteile für Ihre Methode vorschlagen. Dieser Kommentar ist wirklich anstößig und Sie sollten keinen solchen Kommentar hinzufügen.
Dilip

1
Es tut mir leid, dass es Sie beleidigt hat, Dilip, aber das Schreiben von 2 * M_PI ist objektiv klarer als ((360 * M_PI) / 180), und das Schreiben des letzteren zeigt einen Mangel an Verständnis für das jeweilige Thema.
BreadicalMD

1
@BreadicalMD Keine Sorge, aber es ist ein guter Vorschlag, ich habe meinen Code aktualisiert. Danke.
Dilip

9

Hier ist meine schnelle Lösung als UIView-Erweiterung. Dies kann als Simulation eines UIActivityIndicator-Verhaltens in einem beliebigen UIImageView betrachtet werden.

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

}

9

Eine Swift3-Version:

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

Und denken Sie daran nennen startRotatein viewWillAppearin nicht viewDidLoad.


3

Sie können dieselbe Art von Animation auch mit UIView und Blöcken ausführen. Hier ist eine Klassenerweiterungsmethode, mit der die Ansicht um einen beliebigen Winkel gedreht werden kann.

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

Würde diese Methode (rekursiver Aufruf nach Abschluss) mit einer sich schnell ändernden Animation gut funktionieren? Zum Beispiel wäre die Dauer ca. 1 / 30s, damit ich die Rotationsgeschwindigkeit des Objekts in Echtzeit ändern kann, indem ich beispielsweise auf Berührungen reagiere?
Alecail

3

Wenn jemand die Lösung von Nates wollte, aber schnell, dann ist hier eine grobe schnelle Übersetzung:

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

3

für 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");
}

2

So drehe ich 360 in die richtige Richtung.

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

2

Erstellen Sie die 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;
}

Fügen Sie es einer solchen Ansicht hinzu

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

Wie unterscheidet sich diese Antwort? Sie werden viel saubereren Code haben, wenn die meisten Ihrer Funktionen Objekte zurückgeben, anstatt nur einige Objekte hier und da zu manipulieren.


2

Es gibt verschiedene Möglichkeiten, eine 360-Grad-Animation mit UIView durchzuführen.

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


Hier ist eine Erweiterungsfunktion für UIView, die Start- und Stopp-Rotationsvorgänge verarbeitet:

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


Jetzt mit UIView.animation Closure:

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

2

Die Antwort von @ ram war wirklich hilfreich. Hier ist eine Swift-Version der Antwort.

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

Wie kann ich die Rotation stoppen?
user2526811

1
Wie wäre es, wenn Sie eine Flagge setzen? Also, vielleicht haben Sie eine Funktion zum Stoppen der Animation: var stop = false private func stopRotation() { stop = true } Dann drinnen if finished {...}:if finished { if stop { return} self.rotateImageView() }
Yoninja

Danke, ich habe genau das Gleiche getan. Vielen Dank für Ihre Antwort.
user2526811

1

Ich habe ein glänzendes Animations-Framework entwickelt, mit dem Sie Zeit sparen können! Mit dieser Animation kann sehr einfach erstellt werden:

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

Um diese Animation zu stoppen, setzen Sie einfach nilauf endlessRotater.

Wenn Sie interessiert sind, schauen Sie bitte: https://github.com/hip4yes/Animatics


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

Ref


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

1

Swift 5 UIView-Erweiterung mit Keyframe-Animationen

Dieser Ansatz ermöglicht es uns, die UIView.AnimationOptions.repeat direkt zu verwenden

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)

    }

}

2
Ich mag deine, auch ziemlich sauber
Nicolas Manzini

0

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


    }

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

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

-1

Ich denke, Sie sollten besser eine UIVIewKategorie hinzufügen :

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

Implementierung UIView (Drehen)

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

Nicht mit diesen Methoden :)

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

Gut gemacht. Ich möchte nur die Kategorie UIImageView kommentieren. Trotzdem danke.
Zgpeace
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.