Annullare un'animazione UIView?


244

È possibile annullare UIViewun'animazione mentre è in corso? O dovrei scendere al livello CA?

cioè ho fatto qualcosa del genere (magari impostando anche un'azione di fine animazione):

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

// set view properties

[UIView commitAnimations];

Ma prima che l'animazione si completi e ricevo l'evento che termina, voglio annullarlo (abbreviato). È possibile? Googling trova alcune persone che fanno la stessa domanda senza risposte - e una o due persone che ipotizzano che non si possa fare.


Intendi mettere in pausa l'animazione nel mezzo e lasciarla lì? O tornare a dove era all'inizio?
Chris Lundie,

2
O lasciandolo esattamente dove si trova, a metà animazione (in pratica inizierò un'altra animazione subito dopo), o salterà direttamente al punto finale. Immagino che il primo sia più naturale, ma in entrambi i casi funziona per me.
philsquared,

3
@ Chris Hanson: apprezzo le tue modifiche, ma mi chiedo quale sia la tua logica per la rimozione del tag iPhone. Potrebbe essere implicito nel tocco di cacao, ma io per primo ho un filtro tag su iPhone e in questo caso mi mancherebbe.
philsquared,

@Boot To The Head (di nuovo): la tua domanda e la mia risposta ad essa, mi hanno spinto a testare un po 'di più in isolamento e ho scoperto che se avvii una nuova animazione prima che la prima sia terminata, salta al punto finale prima di iniziare quello nuovo.
philsquared,

- questo soddisfa i miei bisogni immediati (e suggerisce che ho qualche altro bug nel mio codice reale), ma lascerò aperta questa domanda poiché so che altri hanno chiesto lo stesso.
philsquared,

Risposte:


114

Il modo in cui lo faccio è creare una nuova animazione fino al punto finale. Impostare una durata molto breve e assicurarsi di utilizzare il +setAnimationBeginsFromCurrentState:metodo per iniziare dallo stato corrente. Quando lo si imposta su SÌ, l'animazione corrente viene interrotta. Sembra qualcosa del genere:

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

// set view properties

[UIView commitAnimations];

Grazie Stefano. In realtà, se leggi i commenti sotto la mia domanda, questo è esattamente quello che ho fatto alla fine - ma dato che hai dato un resoconto chiaro e chiaro di ciò qui, lo
segnerò

1
@Vojto In che modo? I documenti dicono che l'uso di setAnimationBeginsFromCurrentState:è scoraggiato da iOS 4 ma è ancora lì e dovrebbe ancora funzionare.
Stephen Darlington,

55
in iOS4 +, usa l'opzione UIViewAnimationOptionBeginFromCurrentState su animateWithDuration: ritardo: opzioni: animazioni: completamento:
Adam Eberbach

UIViewAnimationOptionBeginFromCurrentState annullerà qualsiasi animazione in corso o solo un'animazione destinata alla stessa proprietà? Se è un'animazione, come puoi farla continuare una nuova animazione nello stesso punto SENZA interrompere la stessa animazione in corso?
zakdances,

1
@yourfriendzak, basato su un breve test, sembra che annulli solo un'animazione destinata alla stessa proprietà. Ho provato a cambiare l'alfa di uno scrollView il cui contentOffset è stato animato e l'animazione contentOffset ha continuato.
arlomedia,

360

Uso:

#import <QuartzCore/QuartzCore.h>

.......

[myView.layer removeAllAnimations];

22
Ho scoperto che dovevo aggiungere #import <QuartzCore / QuartzCore.h> per rimuovere un avviso del compilatore;)
deanWombourne,

4
Così semplice, eppure così difficile da trovare. Grazie per questo, ho appena trascorso un'ora a cercare di capirlo.
MikeyWard,

4
Il possibile problema con questa soluzione è che (almeno in iOS 5) c'è un ritardo prima che abbia effetto - non funziona abbastanza presto se ciò che vuoi dire è "ferma l'animazione adesso, prima di passare alla riga successiva di codice".
matt

14
Ho scoperto che chiamare questo innesca il completamento: ^ (BOOL finito) blocco se si utilizza + (void) animateWithDuration: (NSTimeInterval) animazioni di durata: (void (^) (void)) completamento animazioni: (void (^) (BOOL finito) ) completamento
JWGS

1
@matt conosci una soluzione che ferma subito l'animazione? sto osservando anche un piccolo ritardo prima che l'animazione si fermi davvero.
Tiguero,

62

Il modo più semplice per interrompere immediatamente tutte le animazioni in una vista particolare è questo:

Collega il progetto a QuartzCore.framework. All'inizio del tuo codice:

#import <QuartzCore/QuartzCore.h>

Ora, quando vuoi interrompere tutte le animazioni su una vista morta nelle loro tracce, dì questo:

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

La linea di mezzo funzionerebbe da sola, ma c'è un ritardo fino al termine del runloop (il "momento di ridisegno"). Per evitare tale ritardo, avvolgere il comando in un blocco di transazione esplicito come mostrato. Funziona a condizione che non siano state apportate altre modifiche su questo livello nel runloop corrente.


2
Sto usando [CATransaction flush] dopo che il tuo codice funziona alla grande, a volte perché non scrive immediatamente. Tuttavia, esiste un problema di prestazioni di 0,1 secondi, ad esempio in ritardo per quel particolare momento. Qualche soluzione alternativa?
Sidwyn Koh

5
removeAllAnimationsavrà l'effetto collaterale di portare l'animazione al suo fotogramma finale, piuttosto che fermarlo dove si trova in questo momento.
Ilya,

3
Normalmente, quando si vuole che un'animazione si fermi, si aspettano che si fermi nel suo frame corrente - non nel primo frame né nell'ultimo frame. Saltare al primo o all'ultimo fotogramma sembrerà un movimento a scatti.
Ilya,

1
Descriverei quindi la risposta per specificare qual è il comportamento predefinito e come può essere modificato. Ovviamente qualcuno che fa animazione è attivamente interessato a ciò che accade sullo schermo seguendo le sue chiamate di metodo :)
Ilya

1
L'ho dettagliato altrove. Non è quello che mi è stato chiesto, quindi non è quello che ho risposto qui. Se hai un'altra risposta, sicuramente dai come risposta!
opaco

48

Su iOS 4 e versioni successive, utilizza l' UIViewAnimationOptionBeginFromCurrentStateopzione sulla seconda animazione per abbreviare la prima animazione.

Ad esempio, supponiamo di avere una vista con un indicatore di attività. Si desidera dissolvere l'indicatore di attività mentre inizia un'attività potenzialmente dispendiosa in termini di tempo e svanire al termine dell'attività. Nel codice seguente viene chiamata la vista con l'indicatore di attività 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;
                     }
                 }];
}

41

Per annullare un'animazione è sufficiente impostare la proprietà che viene attualmente animata, al di fuori dell'animazione UIView. Ciò interromperà l'animazione ovunque si trovi e l'UIView passerà all'impostazione appena definita.


9
Questo è solo parzialmente vero. Arresta l'animazione, ma non impedisce di attivare i metodi delegati, in particolare il gestore di animazione Completa, quindi se hai la logica lì, vedrai dei problemi.
M. Ryan,

33
Ecco a cosa -animationDidStop:finished:context:serve l' argomento "finito" . Se [finished boolValue] == NO, allora sai che la tua animazione è stata in qualche modo cancellata.
Nick Forge,

questo è un ottimo consiglio di 7 anni fa! nota che c'è un comportamento strano: supponiamo che tu stia animando alfa avanti e indietro e lo fai "vai a 0". per FERMARE l'animazione, non è possibile impostare alpha su zero; lo hai impostato per dire 1.
Fattie,

26

Mi dispiace risorgere questa risposta, ma in iOS 10 le cose sono cambiate, e ora è possibile annullare e puoi anche annullare con grazia!

Dopo iOS 10 puoi annullare le animazioni con UIViewPropertyAnimator !

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

Se passi true annulla l'animazione e si ferma proprio dove l'hai annullata. Il metodo di completamento non verrà chiamato. Tuttavia, se passi falso sei responsabile del completamento dell'animazione:

animator.finishAnimation(.start)

È possibile completare l'animazione e rimanere nello stato corrente (.current) o passare allo stato iniziale (.start) o allo stato finale (.end)

A proposito, puoi anche mettere in pausa e riavviare in seguito ...

animator.pauseAnimation()
animator.startAnimation()

Nota: se non si desidera una cancellazione improvvisa, è possibile invertire l'animazione o persino modificare l'animazione dopo averla messa in pausa!


2
Questo è sicuramente il più rilevante per le animazioni moderne.
Doug,

10

Se stai animando un vincolo modificando la costante anziché una proprietà di visualizzazione, nessuno degli altri metodi funziona su iOS 8.

Esempio di animazione:

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

                 }];

Soluzione:

È necessario rimuovere le animazioni dai livelli di tutte le viste interessate dalla modifica del vincolo e dai relativi sublayer.

[self.constraintView.layer removeAllAnimations];
for (CALayer *l in self.constraintView.layer.sublayers)
{
    [l removeAllAnimations];
}

1
self.constraintView.layer.removeAllAnimations()Funziona anche solo (Swift)
Lachtan

Nessuna delle altre soluzioni ha funzionato. Grazie, ha funzionato per me.
swapnilagarwal,


5

Nessuna delle precedenti ha risolto il problema per me, ma questo mi ha aiutato: l' UIViewanimazione imposta immediatamente la proprietà, quindi la anima. Arresta l'animazione quando il livello di presentazione corrisponde al modello (la proprietà set).

Ho risolto il mio problema, che era "Voglio animare da dove sembri apparire" ("tu" significa la vista). Se lo vuoi, allora:

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

imposta la posizione sul livello di presentazione

Uso alcune opzioni tra cui UIViewAnimationOptionOverrideInheritedDuration

Ma poiché la documentazione di Apple è vaga, non so se sostituisce davvero le altre animazioni quando viene utilizzata o reimposta i timer.

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

E quella dovrebbe essere una soluzione decente per ora.


5
[UIView setAnimationsEnabled:NO];
// your code here
[UIView setAnimationsEnabled:YES];

2

Versione rapida della soluzione di Stephen Darlington

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

// set view properties
UIView.commitAnimations()

1

Ho lo stesso problema; le API non hanno nulla per annullare alcune animazioni specifiche. Il

+ (void)setAnimationsEnabled:(BOOL)enabled

disabilita TUTTE le animazioni e quindi non funziona per me. Esistono due soluzioni:

1) trasforma il tuo oggetto animato in una vista secondaria. Quindi, quando si desidera annullare le animazioni per quella vista, rimuovere la vista o nasconderla. Molto semplice, ma è necessario ricreare la vista secondaria senza animazioni se è necessario tenerla in vista.

2) ripeti l'animazione solo uno e crea un selettore delegato per riavviare l'animazione se necessario, in questo modo:

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

Il problema con 2) è che c'è sempre un ritardo nell'arrestare l'animazione al termine del ciclo di animazione corrente.

Spero che questo ti aiuti.


1

Anche se si annulla l'animazione nei modi sopra l'animazione didStopSelectorcontinua a essere eseguita. Pertanto, se nella propria applicazione sono presenti stati logici guidati da animazioni, si avranno problemi. Per questo motivo con le modalità sopra descritte uso la variabile di contesto delle UIViewanimazioni. Se si passa lo stato corrente del programma dal parametro di contesto all'animazione, quando l'animazione si interrompe la didStopSelectorfunzione può decidere se deve fare qualcosa o tornare semplicemente in base allo stato corrente e il valore di stato passato come contesto.


NickForge lo spiega perfettamente nel commento sotto la risposta corretta di TomTaylor sopra ...
Fattie,

Grazie mille per questa nota! In effetti ho una situazione del genere, in cui una successiva animazione viene attivata quando viene chiamato animationDidStop. Se rimuovi semplicemente un'animazione e ne aggiungi immediatamente una nuova per la stessa chiave, trovo che non funzionerà. Devi aspettare, ad esempio fino a quando animazioneDidStop si innesca per un'altra animazione o qualcos'altro. L'animazione che è stata appena rimossa non genererà più animazioneDidStop.
RickJansen,

0
CALayer * pLayer = self.layer.presentationLayer;
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView animateWithDuration:0.001 animations:^{
    self.frame = pLayer.frame;
}];

0

Per mettere in pausa un'animazione senza ripristinare lo stato originale o finale:

CFTimeInterval paused_time = [myView.layer convertTime:CACurrentMediaTime() fromLayer:nil];
myView.layer.speed = 0.0;
myView.layer.timeOffset = paused_time;

0

Quando lavoro con l' UIStackViewanimazione, inoltre, removeAllAnimations()devo impostare alcuni valori su uno iniziale perché removeAllAnimations()possono impostarli su uno stato imprevedibile. Ho stackViewcon view1e view2dentro, e una vista dovrebbe essere visibile e una nascosta:

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

0

Nessuna delle soluzioni risposte ha funzionato per me. Ho risolto i miei problemi in questo modo (non so se sia un modo corretto?), Perché avevo problemi a chiamarlo troppo velocemente (quando l'animazione precedente non era ancora finita). Passo l'animazione desiderata con il blocco 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()
                }
        })

    }

}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.