UIView Nieskończona animacja obrotu o 360 stopni?

251

Próbuję obrócić o UIImageView360 stopni i obejrzałem kilka samouczków online. Nie mogłem sprawić, by żaden z nich pracował, bez UIViewzatrzymywania się i przeskakiwania do nowej pozycji.

  • Jak mogę to osiągnąć?

Najnowsza rzecz, której próbowałem, to:

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

Ale jeśli użyję 2 * pi, to w ogóle się nie porusza (ponieważ jest w tej samej pozycji). Jeśli spróbuję zrobić tylko pi (180 stopni), to działa, ale jeśli ponownie wywołam metodę, obraca się do tyłu.

EDYCJA :

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

też nie działa. Zmienia się w 180stopnie, zatrzymuje na ułamek sekundy, a następnie resetuje z powrotem do 0stopni, zanim zacznie się od nowa.

Derek
źródło

Odpowiedzi:

334

Znalazłem metodę (nieco ją zmodyfikowałem), która działała idealnie dla mnie: obrót 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
źródło
25
#import <QuartzCore / QuartzCore.h>
AlBeebe
3
dodaj Projekt QuartzCore.framework-> Fazy kompilacji-> Linkowe pliki binarne
gjpc
9
Jest to właściwy kod dla iOS 3.0 i niższych, ale dla nowszych programistów i nowych projektów Apple ostrzega teraz użytkowników przed tymi metodami, ponieważ kod Docs & @Nate używa animacji blokowych, które preferuje teraz Apple
PaulWoodIII
1
@cvsguimaraes Z dokumentu: „Określa, czy wartość właściwości jest wartością na końcu poprzedniego cyklu powtarzania plus wartość bieżącego cyklu powtarzania”.
smad
12
To może pomóc komuś , [view.layer removeAllAnimations]; aby zatrzymać animację w razie potrzeby.
arunit21
112

Wyrazy uznania dla Richarda J. Rossa III za pomysł, ale odkryłem, że jego kod nie był dokładnie tym, czego potrzebowałem. optionsWierzę, że domyślnie daje ci to UIViewAnimationOptionCurveEaseInOut, co nie wygląda dobrze w ciągłej animacji. Dodałem też czek, aby w razie potrzeby zatrzymać animację nawet o ćwierć obrotu (nie nieskończony , ale o nieokreślonym czasie trwania), a przyspieszenie przyspieszyło podczas pierwszych 90 stopni i zwalniało podczas ostatnich 90 stopni (po zażądaniu zatrzymania):

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

Aktualizacja

Dodałem możliwość obsługi próśb o ponowne rozpoczęcie wirowania ( startSpin), podczas gdy poprzedni spin się kończy (kończy). Przykładowy projekt tutaj na Github .

Nate
źródło
2
Korzystając z tego, zauważam krótką przerwę w animacji pod każdym kątem PI / 2 (90 stopni) i marginalny wzrost użycia procesora w stosunku do wybranej odpowiedzi za pomocą CABasicAnimation. Metoda CABasicAnimation zapewnia bezbłędnie płynną animację.
Arjun Mehta
1
@Dilip wymienić M_PI / 2z - (M_PI / 2). (Przewiń kod w prawo, aby zobaczyć koniec każdej linii)
Nate
1
@Dipip, nie wiem, jakiego czasu używasz w kodzie. Te same 0.5sekundy na 90 stopni, co ja? Jeśli tak, mój kod nie pozwala zatrzymać się o ułamek obrotu o 90 stopni. Pozwala zatrzymać się w pełnej liczbie 90-stopniowych odstępów, ale dodaje jeden dodatkowy obrót o 90 stopni, „łagodniejszy”. Tak więc, w powyższym kodzie, jeśli zadzwonisz stopSpinpo 0,35 sekundy, będzie wirować przez kolejne 0,65 sekundy i zatrzyma się przy całkowitym obrocie o 180 stopni. Jeśli masz bardziej szczegółowe pytania, prawdopodobnie powinieneś otworzyć nowe pytanie. Link do tej odpowiedzi jest bezpłatny.
Nate
1
Co widzisz, @MohitJethwa? Mam aplikację, która tego używa, i nadal działa dla mnie na iOS 8.1.
Nate,
1
@Hemang, jasne, ale nie o to chodziło. Zadałbym nowe pytanie, czy właśnie to próbujesz zrobić.
Nate
85

W Swift możesz użyć następującego kodu do nieskończonej rotacji:

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

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

Zatrzymywanie jest jak:

func stopRotatingView(view: UIView) {
    if view.layer.animationForKey(kRotationAnimationKey) != nil {
        view.layer.removeAnimationForKey(kRotationAnimationKey)
    }
}
Kádi
źródło
Co to jest kRotationAnimationKey?
Luda
Jest to stały klucz String, który pomaga zidentyfikować i przeszukać animację. Może to być dowolny ciąg znaków. W procesie usuwania możesz zobaczyć, że animacja jest przeszukiwana, a następnie usuwana przez ten klucz.
Kádi
Czy to jakiś ciąg, czy coś konkretnego?
Luda
Przepraszamy za późną odpowiedź, ale tak, może to być dowolny ciąg.
Kádi,
1
// Dowolny klucz jak w odpowiedzi
Zander Zhang
67

Powyższa odpowiedź Nate'a jest idealna do zatrzymania i uruchomienia animacji i daje lepszą kontrolę. Byłem zaintrygowany, dlaczego twój nie działa, a jego działa. Chciałem podzielić się tutaj swoimi odkryciami i prostszą wersją kodu, który animowałby UIView w sposób ciągły bez przeciągania.

To jest kod, którego użyłem,

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

Użyłem „CGAffineTransformRotate” zamiast „CGAffineTransformMakeRotation”, ponieważ ten pierwszy zwraca wynik, który jest zapisywany w trakcie animacji. Zapobiegnie to przeskakiwaniu lub resetowaniu widoku podczas animacji.

Inną rzeczą jest nieużywanie „UIViewAnimationOptionRepeat”, ponieważ pod koniec animacji, zanim zacznie się powtarzać, resetuje transformację, powodując, że widok przeskakuje z powrotem do pierwotnej pozycji. Zamiast powtarzania powtarzasz się, aby transformacja nigdy nie była resetowana do pierwotnej wartości, ponieważ blok animacji praktycznie nigdy się nie kończy.

Ostatnią rzeczą jest przekształcenie widoku w krokach co 90 stopni (M_PI / 2) zamiast 360 lub 180 stopni (2 * M_PI lub M_PI). Ponieważ transformacja zachodzi jako mnożenie macierzy wartości sinus i cosinus.

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

Powiedzmy, że jeśli użyjesz transformacji 180 stopni, cosinus 180 daje -1, co powoduje, że widok przekształca się za każdym razem w przeciwnym kierunku (odpowiedź Note-Nate'a również będzie miała ten problem, jeśli zmienisz wartość radianową transformacji na M_PI). Transformacja 360 stopni po prostu prosi widok, aby pozostał tam, gdzie był, dlatego nie widać żadnego obrotu.

Baran
źródło
Cały sens mojej odpowiedzi (i odpowiedzi Richarda, z której się wywodzi) polega na zastosowaniu kroków 90 stopni , aby uniknąć zmiany kierunku. 90 stopni działa również stosunkowo dobrze, jeśli kiedykolwiek chcesz zatrzymać obrót , ponieważ wiele obrazów ma symetrię 180 stopni lub 90 stopni.
Nate
Tak, właśnie pomyślałem, że wyjaśnię, dlaczego jest używany :)
ram
2
@nik Ale po ustawieniu tam punktu przerwania widzę, że nie byłoby przepełnienia stosu, ponieważ system uzupełniający jest wywoływany przez system, do tego czasu rotateImageViewjuż się zakończył. W tym sensie nie jest to tak naprawdę rekurencyjne.
huggie
1
Dobra odpowiedź +1 dla małej wersji kodu z pożądaną funkcjonalnością.
Pradeep Mittal
1
Naprawdę doceniam wyjaśnienie, dlaczego nie używać UIViewAnimationOptionRepeat.
Jonathan Zhan
21

Jeśli wszystko, co chcesz zrobić, to obracać obraz bez końca, działa to całkiem dobrze i jest bardzo proste:

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

Z mojego doświadczenia wynika, że ​​działa to bezbłędnie, ale upewnij się, że twój obraz może być obracany wokół jego środka bez żadnych przesunięć, w przeciwnym razie animacja obrazu „przeskoczy”, gdy zbliży się do PI.

Aby zmienić kierunek obrotu, zmień znak angle( angle *= -1).

Aktualizuj komentarze @AlexPretzlav zmusił mnie do ponownej wizyty i zdałem sobie sprawę, że kiedy to napisałem, obraz, który obracałem, był odbijany zarówno wzdłuż osi pionowej, jak i poziomej, co oznacza, że ​​obraz rzeczywiście obracał się tylko o 90 stopni, a następnie resetował, chociaż wyglądał jak cały czas się obracał.

Tak więc, jeśli twój obraz jest podobny do mojego, zadziała to świetnie, jednak jeśli obraz nie jest symetryczny, zauważysz „przyciąganie” z powrotem do pierwotnej orientacji po 90 stopniach.

Aby obrócić obraz niesymetryczny, lepiej jest zaakceptować odpowiedź.

Jedno z tych mniej eleganckich rozwiązań, pokazane poniżej, naprawdę obróci obraz, ale po ponownym uruchomieniu animacji może wystąpić zauważalne zacinanie:

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

Możesz to zrobić tylko z blokami, jak sugeruje @ richard-j-ross-iii, ale otrzymasz ostrzeżenie o pętli zatrzymania, ponieważ blok przechwytuje się:

__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();
lewigroker
źródło
1
To tylko dla mnie obróciło o 1/4 obrotu.
Alex Pretzlav
@AlexPretzlav Upewnij się, że UIViewAnimationOptionRepeatustawiłeś animację options.
levigroker
1
Zrobiłem to, obraca się o 45 °, a następnie zapętla się, skacząc z powrotem do 0 ° i ponownie obracając 45 °
Alex Pretzlav
Zaktualizowałem moją odpowiedź o nowe informacje o tym, dlaczego to „zadziałało”
levigroker
21

Mój wkład w rozszerzenie Swift ze sprawdzonego rozwiązania:

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

Przestarzałe:

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
źródło
Do liczenia powtórzeń można użyć .infinity.
Pan Rogers
15

Niesamowita odpowiedź Davida Rysanka zaktualizowana do 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.
źródło
działał dla mnie bardzo dobrze (na UIButton). Dziękuję Ci!
agrippa
podobała mi się twoja prostota
Nicolas Manzini
10

Użyj ćwierć obrotu i zwiększaj stopniowo.

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
źródło
Jestem bardzo nowy w blokach, ale ta metoda zgłasza błąd „Niezgodne typy wskaźników blokowych wysyłają„ void (^ const__strong () ”do parametru typu„ void (^) (BOOL) ”. Próbowałem zmienić metodę rotacji w kodzie w moim pytaniu do imageToMove.transform = CGAffineTransformRotate (imageToMove.transform, M_PI / 2); użyłeś, a animacja wciąż ma niewielkie opóźnienie i resetuje się przed następną turą
Derek
10

To działało dla mnie:

[UIView animateWithDuration:1.0
                animations:^
{
    self.imageView.transform = CGAffineTransformMakeRotation(M_PI);
    self.imageView.transform = CGAffineTransformMakeRotation(0);
}];
inigo333
źródło
To się nie dzieje przez nieskończone czasy!
autor: Jeevan
9

W tym repozytorium znalazłem ładny kod ,

Oto kod z niego, zrobiłem małe zmiany zgodnie z moją potrzebą szybkości :)

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
źródło
@BreadicalMD, Matematyka jest jak programowanie, możesz zrobić to samo na wiele sposobów. To nie znaczy, że tylko jedna droga jest poprawna, a inne nie. Tak, mogą być pewne zalety i wady na każdy sposób, ale to nie znaczy, że możesz zadać pytanie o czyjąś metodę programowania, Możesz zasugerować wady dla tej metody i zalety dla twojej metody. Ten komentarz jest naprawdę obraźliwy i nie należy dodawać takiego komentarza.
Dilip
1
Przepraszam, że cię obraziłem, Dilip, ale pisanie 2 * M_PI jest obiektywnie wyraźniejsze niż ((360 * M_PI) / 180), a pisanie tego drugiego pokazuje brak zrozumienia omawianego tematu.
BreadicalMD
1
@BreadicalMD Nie martw się, ale to dobra sugestia, zaktualizowałem swój kod. Dzięki
Dilip
9

Oto moje szybkie rozwiązanie jako rozszerzenie UIView. Można to uznać za symulację zachowania UIActivityIndicator na dowolnym 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
źródło
9

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

I pamiętaj, aby zadzwonić startRotatena viewWillAppearnie viewDidLoad.

Victor Choy
źródło
3

Możesz także wykonać ten sam typ animacji za pomocą UIView i bloków. Oto metoda rozszerzenia klasy, która może obracać widok o dowolny kąt.

- (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
źródło
Czy ta metoda (rekursywne wywołanie po zakończeniu) działałaby dobrze z szybko zmieniającą się animacją? Na przykład czas trwania wyniósłby około. 1 / 30s, abym mógł modyfikować prędkość obrotu obiektu w czasie rzeczywistym, na przykład reagując na dotyk?
Alecail
3

Jeśli ktoś chciałby rozwiązania Natesa, ale szybko, oto szorstkie szybkie tłumaczenie:

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
źródło
3

dla 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
źródło
2

W ten sposób obracam 360 we właściwym kierunku.

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

Utwórz animację

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

Dodaj go do takiego widoku

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

Czym różni się ta odpowiedź? Będziesz miał znacznie czystszy kod, jeśli większość funkcji zwraca obiekty zamiast tylko manipulować niektórymi obiektami tu i tam.

hfossli
źródło
2

Istnieją różne sposoby wykonywania animacji 360 stopni za pomocą UIView.

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


Oto funkcje rozszerzenia dla UIView, które obsługują operacje rozpoczynania i zatrzymywania obrotu:

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


Teraz używa zamknięcia UIView.animation :

UIView.animate(withDuration: 0.5, animations: { 
      view.transform = CGAffineTransform(rotationAngle: (CGFloat(Double.pi)) 
}) { (isAnimationComplete) in
    // Animation completed 
}
Krunal
źródło
2

@ barani odpowiedź była bardzo pomocna. Oto szybka wersja odpowiedzi.

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

Szybki 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
źródło
jak mogę zatrzymać rotację?
user2526811,
1
A może postawisz flagę? Więc może masz func do zatrzymania animacji: var stop = false private func stopRotation() { stop = true } Następnie w środku if finished {...}:if finished { if stop { return} self.rotateImageView() }
yoninja 21.09.16
Dziękuję, dokładnie to samo zrobiłem. Dzięki za twoją odpowiedź.
user2526811,
1

Opracowałem błyszczącą ramę animacji, która pozwala zaoszczędzić czas! Za pomocą tej animacji można bardzo łatwo stworzyć:

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

aby zatrzymać animację po prostu ustawić nilsię endlessRotater.

Jeśli jesteś zainteresowany, spójrz: https://github.com/hip4yes/Animatics

Nikita Arkhipov
źródło
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()
        }
    }

Nr ref

Mohammad Zaid Pathan
źródło
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
źródło
1

Rozszerzenie UIView Swift 5 przy użyciu animacji klatek kluczowych

Takie podejście pozwala nam bezpośrednio korzystać z 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
źródło
1
Lubię twoje, też całkiem czyste
Nicolas Manzini
0

Szybki:

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
źródło
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
źródło
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
źródło
-1

Myślę, że powinieneś dodać UIVIewkategorię:

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

Implementacja UIView (Obracanie)

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

Nie korzystam z tych metod :)

  • (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
źródło
Dobra robota. Chcę tylko skomentować kategorię UIImageView. Dziękuję wszystkim tym samym.
Zgpeace,