Anulować animację UIView?

244

Czy można anulować trwającą UIViewanimację? Czy też musiałbym zejść na poziom CA?

tzn. zrobiłem coś takiego (może też ustawiam akcję animacji końcowej):

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:duration];
[UIView setAnimationCurve: UIViewAnimationCurveLinear];
// other animation properties

// set view properties

[UIView commitAnimations];

Ale zanim animacja się zakończy i dostanę zdarzenie zakończenia animacji, chcę je anulować (skrócić to). czy to możliwe? Googlowanie po okolicy powoduje, że kilka osób zadaje to samo pytanie bez odpowiedzi - i jedna lub dwie osoby spekulują, że nie da się tego zrobić.

philsquared
źródło
Masz na myśli wstrzymanie animacji na środku i pozostawienie jej tam? Lub wracając do miejsca, w którym było na początku?
Chris Lundie
2
Albo zostaw go dokładnie tam, gdzie jest, animacja środkowa (w praktyce i tak zaraz zacznę kolejną animację), albo przeskocz prosto do punktu końcowego. Wyobrażam sobie, że pierwszy jest bardziej naturalny, ale w tym przypadku działa dla mnie oba.
philsquared
3
@Chris Hanson: Doceniam twoje zmiany, ale zastanawiam się, jakie jest twoje uzasadnienie dla usunięcia tagu iPhone'a. Może to wynikać z kakaowego dotyku, ale ja, na przykład, mam filtr tagów na iPhonie i przegapiłbym to w tym przypadku.
philsquared
@Boot To The Head (ponownie): twoje pytanie i moja własna odpowiedź na nie skłoniły mnie do przetestowania trochę więcej w izolacji i odkryłem, że jeśli rozpoczniesz nową animację przed zakończeniem pierwszej, przeskoczy ona do punktu końcowego przed rozpoczęciem nowego.
philsquared
- odpowiada to moim bezpośrednim potrzebom (i sugeruje, że mam inny błąd w moim prawdziwym kodzie), ale pozostawiam to pytanie otwarte, ponieważ wiem, że inni prosili o to samo.
philsquared

Odpowiedzi:

114

W ten sposób tworzę nową animację do swojego punktu końcowego. Ustaw bardzo krótki czas trwania i upewnij się, że używasz +setAnimationBeginsFromCurrentState:metody, aby zacząć od bieżącego stanu. Po ustawieniu na TAK, bieżąca animacja zostanie skrócona. Wygląda mniej więcej tak:

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.1];
[UIView setAnimationCurve: UIViewAnimationCurveLinear];
// other animation properties

// set view properties

[UIView commitAnimations];
Stephen Darlington
źródło
Dzięki Stephen. Właściwie, jeśli czytasz komentarze pod moim pytaniem, to jest dokładnie to, co zrobiłem w końcu - ale ponieważ podałeś tutaj dobrą, jasną relację, zaznaczam to jako zaakceptowane
philsquared
1
@Vojto Jak to zrobić? Dokumenty mówią, że setAnimationBeginsFromCurrentState:odradza się korzystanie z iOS 4, ale wciąż tam jest i powinno nadal działać.
Stephen Darlington
55
w iOS4 + użyj opcji UIViewAnimationOptionBeginFromCurrentState w programie animateWithDuration: opóźnienie: opcje: animacje: zakończenie:
Adam Eberbach
Czy funkcja UIViewAnimationOptionBeginFromCurrentState anuluje trwającą animację lub tylko animację kierowaną na tę samą właściwość? Jeśli jest to jakaś animacja, jak możesz sprawić, by kontynuowała nową animację w tym samym punkcie BEZ zatrzymywania trwającej tej samej animacji?
zakdances
1
@tworzony_framdzak, na podstawie krótkiego testu, wygląda na to, że anuluje tylko animację kierowaną na tę samą właściwość. Próbowałem zmienić alfa scrollView, którego contentOffset był animowany, a animacja contentOffset była kontynuowana.
arlomedia,
360

Posługiwać się:

#import <QuartzCore/QuartzCore.h>

.......

[myView.layer removeAllAnimations];
Jim Heising
źródło
22
Odkryłem, że muszę dodać #import <QuartzCore / QuartzCore.h>, aby usunąć ostrzeżenie kompilatora;)
deanWombourne
4
Tak prosty, a jednak tak trudny do znalezienia. Dziękuję za to, właśnie spędziłem godzinę próbując to rozgryźć.
MikeyWard,
4
Możliwym problemem związanym z tym rozwiązaniem jest to, że (przynajmniej w iOS 5) występuje opóźnienie, zanim zacznie działać - nie działa wystarczająco szybko, jeśli chodzi o „zatrzymanie animacji w tej chwili, zanim przejdę do następnej linii kod".
mat
14
Odkryłem, że wywołanie tego wyzwala zakończenie: blok ^ (zakończony BOOL), jeśli używasz + (void) animateWithDuration: (NSTimeInterval) czas trwania animacji: (void (^) (void)) zakończenie animacji: (void (^) (BOOL zakończony) ) zakończenie
JWGS
1
@matt Czy znasz rozwiązanie, które natychmiast zatrzymuje animację? obserwuję też małe opóźnienie, zanim animacja naprawdę się zatrzyma.
tiguero
62

Najprostszym sposobem, aby zatrzymać wszystkie animacje na konkretnym zdaniem natychmiast , to:

Połącz projekt z QuartzCore.framework. Na początku kodu:

#import <QuartzCore/QuartzCore.h>

Teraz, gdy chcesz zatrzymać wszystkie animacje w widoku martwe w ich ścieżkach, powiedz to:

[CATransaction begin];
[theView.layer removeAllAnimations];
[CATransaction commit];

Linia środkowa działałaby sama z siebie, ale opóźnienie runloopa dobiegło końca („moment przerysowania”). Aby zapobiec temu opóźnieniu, zawiń polecenie w jawnym bloku transakcji, jak pokazano. Działa to pod warunkiem, że nie dokonano żadnych innych zmian na tej warstwie w bieżącym runloopie.

matowy
źródło
2
Używałem [CATransaction flush] po twoim kodzie, który działa świetnie, czasem dlatego, że nie pisze natychmiast. Jednak występuje problem z wydajnością wynoszącą 0,1 sekundy, np. Opóźnia się w tym konkretnym czasie. Jakieś obejścia?
Sidwyn Koh
5
removeAllAnimationsbędzie miało efekt uboczny, doprowadzając animację do ostatecznej klatki, zamiast zatrzymywać ją tam, gdzie jest teraz.
Ilya,
3
Zwykle, gdy chcemy zatrzymać animację, oczekujemy, że zatrzyma się ona w bieżącej klatce - nie w pierwszej klatce ani w ostatniej klatce. Przeskakiwanie do pierwszej lub ostatniej klatki będzie wyglądało jak gwałtowny ruch.
Ilya,
1
Następnie szczegółowo opisałbym odpowiedź, aby określić, jakie jest domyślne zachowanie i jak można je zmienić. Oczywiście ktoś wykonujący animację jest aktywnie zainteresowany tym, co dzieje się na ekranie po jego wywołaniach metod :)
Ilya
1
Opisałem to gdzie indziej. Nie o to pytano, więc nie na to tutaj odpowiedziałem. Jeśli masz inną odpowiedź, zdecydowanie podaj ją jako odpowiedź!
mat.
48

W systemie iOS 4 i nowszym użyj UIViewAnimationOptionBeginFromCurrentStateopcji drugiej animacji, aby skrócić pierwszą animację.

Jako przykład załóżmy, że masz widok ze wskaźnikiem aktywności. Chcesz zanikać wskaźnik aktywności, gdy zaczyna się aktywność, która może być czasochłonna, i zanikać, gdy aktywność zostanie zakończona. W poniższym kodzie wywoływany jest widok ze wskaźnikiem aktywności activityView.

- (void)showActivityIndicator {
    activityView.alpha = 0.0;
    activityView.hidden = NO;
    [UIView animateWithDuration:0.5
                 animations:^(void) {
                     activityView.alpha = 1.0;
                 }];

- (void)hideActivityIndicator {
    [UIView animateWithDuration:0.5
                 delay:0 options:UIViewAnimationOptionBeginFromCurrentState
                 animations:^(void) {
                     activityView.alpha = 0.0;
                 }
                 completion:^(BOOL completed) {
                     if (completed) {
                         activityView.hidden = YES;
                     }
                 }];
}
Ville Laurikari
źródło
41

Aby anulować animację, wystarczy ustawić właściwość, która jest obecnie animowana, poza animacją UIView. To zatrzyma animację gdziekolwiek się pojawi, a UIView przejdzie do właśnie zdefiniowanego ustawienia.

tomtaylor
źródło
9
To tylko częściowo prawda. Zatrzymuje animację, ale nie powstrzymuje jej przed uruchamianiem metod delegowania, w szczególności programu obsługi animacjiComplete, więc jeśli masz logikę, zobaczysz problemy.
M. Ryan,
33
Po to jest argument „gotowy” -animationDidStop:finished:context:. Jeśli [finished boolValue] == NO, to wiesz, że twoja animacja została jakoś anulowana.
Nick Forge
to świetna wskazówka sprzed 7 lat! Zauważ, że zachowuje się dziwnie: powiedz, że animujesz alfa w przód i w tył i sprawiasz, że „idziesz do 0”. aby zatrzymać animację, nie można ustawić alfa na zero; ustawiłeś na 1.
Fattie
26

Przykro mi, że wskrzesiłem tę odpowiedź, ale w iOS 10 coś się zmieniło, a teraz anulowanie jest możliwe, a nawet możesz z wdziękiem anulować!

Po iOS 10 możesz anulować animacje za pomocą UIViewPropertyAnimator !

UIViewPropertyAnimator(duration: 2, dampingRatio: 0.4, animations: {
    view.backgroundColor = .blue
})
animator.stopAnimation(true)

Jeśli spełnisz warunek, anuluje animację i zatrzyma się tam, gdzie ją anulowałeś. Metoda zakończenia nie zostanie wywołana. Jeśli jednak zdasz fałsz, jesteś odpowiedzialny za ukończenie animacji:

animator.finishAnimation(.start)

Możesz zakończyć animację i pozostać w bieżącym stanie (.current) lub przejść do stanu początkowego (.start) lub do stanu końcowego (.end)

Nawiasem mówiąc, możesz nawet zatrzymać i ponownie uruchomić później ...

animator.pauseAnimation()
animator.startAnimation()

Uwaga: jeśli nie chcesz nagłego anulowania, możesz cofnąć animację lub nawet zmienić animację po jej wstrzymaniu!

Tiago Almeida
źródło
2
Jest to zdecydowanie najbardziej odpowiednie w przypadku nowoczesnych animacji.
Doug
10

Jeśli animujesz ograniczenie, zmieniając stałą zamiast właściwości widoku, żadna inna metoda nie działa na iOS 8.

Przykładowa animacja:

self.constraint.constant = 0;
[self.view updateConstraintsIfNeeded];
[self.view layoutIfNeeded];
[UIView animateWithDuration:1.0f
                      delay:0.0f
                    options:UIViewAnimationOptionCurveLinear
                 animations:^{
                     self.constraint.constant = 1.0f;
                     [self.view layoutIfNeeded];
                 } completion:^(BOOL finished) {

                 }];

Rozwiązanie:

Musisz usunąć animacje z warstw dowolnych widoków, na które wpływa zmiana ograniczenia i ich podwarstw.

[self.constraintView.layer removeAllAnimations];
for (CALayer *l in self.constraintView.layer.sublayers)
{
    [l removeAllAnimations];
}
Nikola Lajic
źródło
1
self.constraintView.layer.removeAllAnimations()Działa także tylko (Swift)
Lachtan 30.04.16
Żadne z pozostałych rozwiązań nie działało, dzięki temu działało dla mnie.
swapnilagarwal
5

Żadne z powyższych nie rozwiązało tego dla mnie, ale to pomogło: UIViewanimacja ustawia właściwość natychmiast, a następnie animuje. Zatrzymuje animację, gdy warstwa prezentacji pasuje do modelu (właściwość set).

Rozwiązałem problem, który brzmiał: „Chcę animować z miejsca, w którym wyglądasz tak, jak wyglądasz” („ty” oznacza widok). Jeśli chcesz, to:

  1. Dodaj QuartzCore.
  2. CALayer * pLayer = theView.layer.presentationLayer;

ustaw pozycję na warstwę prezentacji

Korzystam z kilku opcji, w tym UIViewAnimationOptionOverrideInheritedDuration

Ale ponieważ dokumentacja Apple jest niejasna, nie wiem, czy naprawdę zastępuje inne animacje, gdy są używane, czy po prostu resetuje liczniki.

[UIView animateWithDuration:blah... 
                    options: UIViewAnimationOptionBeginFromCurrentState ... 
                    animations: ^ {
                                   theView.center = CGPointMake( pLayer.position.x + YOUR_ANIMATION_OFFSET, pLayer.position.y + ANOTHER_ANIMATION_OFFSET);
                   //this only works for translating, but you get the idea if you wanna flip and scale it. 
                   } completion: ^(BOOL complete) {}];

I na razie powinno to być przyzwoite rozwiązanie.

NazCodezz
źródło
5
[UIView setAnimationsEnabled:NO];
// your code here
[UIView setAnimationsEnabled:YES];
ideawu
źródło
2

Szybka wersja rozwiązania Stephena Darlington'a

UIView.beginAnimations(nil, context: nil)
UIView.setAnimationBeginsFromCurrentState(true)
UIView.setAnimationDuration(0.1)
// other animation properties

// set view properties
UIView.commitAnimations()
Lucamegh
źródło
1

Mam ten sam problem; interfejsy API nie mają niczego, co by anulowało określoną animację. The

+ (void)setAnimationsEnabled:(BOOL)enabled

wyłącza WSZYSTKIE animacje i dlatego nie działa dla mnie. Istnieją dwa rozwiązania:

1) uczyń z animowanego obiektu widok podrzędny. Następnie, jeśli chcesz anulować animacje dla tego widoku, usuń widok lub ukryj go. Bardzo proste, ale musisz odtworzyć widok cząstkowy bez animacji, jeśli chcesz go wyświetlić.

2) powtórz animację tylko jeden i dokonaj wyboru delegata, aby w razie potrzeby zrestartować animację:

-(void) startAnimation {
NSLog(@"startAnim alpha:%f", self.alpha);
[self setAlpha:1.0];
[UIView beginAnimations:nil context:nil];
[UIView setAnimationDuration:1.0];
[UIView setAnimationRepeatCount:1];
[UIView setAnimationRepeatAutoreverses:YES];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(pulseAnimationDidStop:finished:context:)];
[self setAlpha:0.1];
[UIView commitAnimations];
}

- (void)pulseAnimationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
if(hasFocus) {
    [self startAnimation];
} else {
    self.alpha = 1.0;
}
}

-(void) setHasFocus:(BOOL)_hasFocus {
hasFocus = _hasFocus;
if(hasFocus) {
    [self startAnimation];
}
}

Problem z 2) polega na tym, że zawsze opóźnia się zatrzymanie animacji po zakończeniu bieżącego cyklu animacji.

Mam nadzieję że to pomoże.


źródło
1

Nawet jeśli anulujesz animację w powyższy sposób, animacja didStopSelectornadal działa. Więc jeśli masz stany logiczne w aplikacji sterowane animacjami, będziesz mieć problemy. Z tego powodu w sposób opisany powyżej używam zmiennej kontekstowej UIViewanimacji. Jeśli bieżący stan programu zostanie przekazany przez parametr kontekstu do animacji, gdy animacja się zatrzyma, didStopSelectorfunkcja może zdecydować, czy powinna coś zrobić, czy po prostu powrócić na podstawie bieżącego stanu i wartości stanu przekazanej jako kontekst.

Gorki
źródło
NickForge wyjaśnia to doskonale w komentarzu poniżej poprawnej odpowiedzi
TomTaylora
Wielkie dzięki za tę notatkę! Rzeczywiście mam taką sytuację, w której uruchamiana jest kolejna animacja po wywołaniu animacjiDidStop. Jeśli po prostu usuniesz animację i natychmiast ponownie dodasz nową dla tego samego klucza, to nie będzie działać. Musisz poczekać, na przykład, aż animacjaDidStop wyzwoli kolejną animację lub coś innego. Animacja, która właśnie została usunięta, nie będzie już uruchamiać animacji.
RickJansen,
0
CALayer * pLayer = self.layer.presentationLayer;
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView animateWithDuration:0.001 animations:^{
    self.frame = pLayer.frame;
}];
Królik Grzmotu
źródło
0

Aby wstrzymać animację bez przywracania stanu oryginalnego lub końcowego:

CFTimeInterval paused_time = [myView.layer convertTime:CACurrentMediaTime() fromLayer:nil];
myView.layer.speed = 0.0;
myView.layer.timeOffset = paused_time;
tommybananas
źródło
0

Kiedy pracuję z UIStackViewanimacją, poza tym removeAllAnimations()muszę ustawić niektóre wartości na początkowe, ponieważ removeAllAnimations()mogę ustawić je na nieprzewidywalny stan. Mam stackViewz view1i view2wewnątrz, i jeden widok powinien być widoczny i jeden ukryty:

public func configureStackView(hideView1: Bool, hideView2: Bool) {
    let oldHideView1 = view1.isHidden
    let oldHideView2 = view2.isHidden
    view1.layer.removeAllAnimations()
    view2.layer.removeAllAnimations()
    view.layer.removeAllAnimations()
    stackView.layer.removeAllAnimations()
    // after stopping animation the values are unpredictable, so set values to old
    view1.isHidden = oldHideView1 //    <- Solution is here
    view2.isHidden = oldHideView2 //    <- Solution is here

    UIView.animate(withDuration: 0.3,
                   delay: 0.0,
                   usingSpringWithDamping: 0.9,
                   initialSpringVelocity: 1,
                   options: [],
                   animations: {
                    view1.isHidden = hideView1
                    view2.isHidden = hideView2
                    stackView.layoutIfNeeded()
    },
                   completion: nil)
}
Paul T.
źródło
0

Żadne z odpowiedzi nie zadziałało. Rozwiązałem swoje problemy w ten sposób (nie wiem, czy to poprawny sposób?), Ponieważ miałem problemy z wywołaniem tego zbyt szybko (gdy poprzednia animacja nie była jeszcze ukończona). Mijam pożądaną animację z blokiem customAnim .

extension UIView
{

    func niceCustomTranstion(
        duration: CGFloat = 0.3,
        options: UIView.AnimationOptions = .transitionCrossDissolve,
        customAnim: @escaping () -> Void
        )
    {
        UIView.transition(
            with: self,
            duration: TimeInterval(duration),
            options: options,
            animations: {
                customAnim()
        },
            completion: { (finished) in
                if !finished
                {
                    // NOTE: This fixes possible flickering ON FAST TAPPINGS
                    // NOTE: This fixes possible flickering ON FAST TAPPINGS
                    // NOTE: This fixes possible flickering ON FAST TAPPINGS
                    self.layer.removeAllAnimations()
                    customAnim()
                }
        })

    }

}
sabiland
źródło