Eine UIView-Animation abbrechen?

244

Ist es möglich, eine UIViewAnimation abzubrechen, während sie ausgeführt wird? Oder müsste ich auf die CA-Ebene fallen?

dh ich habe so etwas gemacht (vielleicht auch eine Endanimationsaktion einstellen):

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

// set view properties

[UIView commitAnimations];

Aber bevor die Animation abgeschlossen ist und ich das Ereignis "Animation beendet" erhalte, möchte ich es abbrechen (abkürzen). Ist das möglich? Beim Googeln stellen einige Leute dieselbe Frage ohne Antwort - und ein oder zwei Leute spekulieren, dass dies nicht möglich ist.

philsquared
quelle
Meinen Sie damit, die Animation in der Mitte anzuhalten und dort zu lassen? Oder dorthin zurückkehren, wo es am Anfang war?
Chris Lundie
2
Entweder lassen Sie es genau dort, wo es ist, mitten in der Animation (in der Praxis starte ich sowieso gleich danach eine andere Animation) oder springen Sie direkt zum Endpunkt. Ich würde mir vorstellen, dass das erste natürlicher ist, aber in diesem Fall funktioniert beides für mich.
Philsquared
3
@ Chris Hanson: Ich schätze Ihre Änderungen, frage mich aber, was Ihre Gründe für das Entfernen des iPhone-Tags sind. Es kann durch Kakao-Touch impliziert sein, aber ich habe einen Tag-Filter auf dem iPhone und würde dies in diesem Fall vermissen.
Philsquared
@Boot To The Head (wieder): Ihre Frage und meine eigene Antwort darauf veranlassten mich, ein bisschen mehr isoliert zu testen, und ich stellte fest, dass eine neue Animation, bevor die erste fertig ist, zum Endpunkt springt bevor Sie den neuen starten.
Philsquared
- Dies entspricht meinen unmittelbaren Anforderungen (und legt nahe, dass ich einen anderen Fehler in meinem realen Code habe), aber ich werde diese Frage offen lassen, da ich weiß, dass andere danach gefragt haben.
Philsquared

Antworten:

114

Ich erstelle eine neue Animation für Ihren Endpunkt. Stellen Sie eine sehr kurze Dauer ein und stellen Sie sicher, dass Sie die +setAnimationBeginsFromCurrentState:Methode verwenden, um vom aktuellen Status zu beginnen. Wenn Sie auf JA setzen, wird die aktuelle Animation gekürzt. Sieht ungefähr so ​​aus:

[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
quelle
Danke Stephen. Wenn Sie die Kommentare unter meiner Frage lesen, ist dies genau das, was ich am Ende getan habe - aber da Sie hier einen guten, klaren Bericht darüber abgegeben haben, markiere ich ihn als akzeptiert
philsquared
1
@Vojto Wie so? Die Dokumente sagen, dass die Verwendung von setAnimationBeginsFromCurrentState:von iOS 4 abgeraten wird, aber es ist immer noch da und sollte immer noch funktionieren.
Stephen Darlington
55
Verwenden Sie in iOS4 + die Option UIViewAnimationOptionBeginFromCurrentState für animateWithDuration: Verzögerung: Optionen: Animationen: Abschluss:
Adam Eberbach
Bricht UIViewAnimationOptionBeginFromCurrentState eine laufende Animation oder nur eine Animation ab, die auf dieselbe Eigenschaft abzielt? Wenn es sich um eine Animation handelt, wie können Sie dafür sorgen, dass eine neue Animation an derselben Stelle fortgesetzt wird, ohne dass dieselbe laufende Animation angehalten wird?
Zakdances
1
@yourfriendzak, basierend auf einem kurzen Test, sieht es so aus, als würde nur eine Animation abgebrochen, die auf dieselbe Eigenschaft abzielt. Ich habe versucht, das Alpha einer scrollView zu ändern, deren contentOffset animiert war, und die contentOffset-Animation wurde fortgesetzt.
Arlomedia
360

Verwenden:

#import <QuartzCore/QuartzCore.h>

.......

[myView.layer removeAllAnimations];
Jim Heising
quelle
22
Ich fand, dass ich #import <QuartzCore / QuartzCore.h> hinzufügen musste , um eine Compiler-Warnung zu entfernen;)
deanWombourne
4
So einfach und doch so schwer zu finden. Vielen Dank dafür. Ich habe gerade eine Stunde damit verbracht, dies herauszufinden.
MikeyWard
4
Ein mögliches Problem bei dieser Lösung ist, dass (zumindest in iOS 5) eine Verzögerung auftritt, bevor sie wirksam wird. Sie funktioniert nicht früh genug, wenn Sie damit meinen, dass Sie die Animation sofort stoppen, bevor ich mit der nächsten Zeile fortfahre Code".
Matt
14
Ich fand, dass das Aufrufen dieses Befehls die Vervollständigung auslöst: ^ (BOOL beendet) Block, wenn + (void) animateWithDuration: (NSTimeInterval) Dauer Animationen: (void (^) (void)) Animationen Vervollständigung: (void (^) (BOOL beendet) ) Fertigstellung
JWGS
1
@matt kennst du eine Lösung, die die Animation sofort stoppt? Ich beobachte auch eine kleine Verzögerung, bevor die Animation wirklich aufhört.
Tiguero
62

Der einfachste Weg, alle Animationen in einer bestimmten Ansicht sofort zu stoppen , ist folgender:

Verknüpfen Sie das Projekt mit QuartzCore.framework. Zu Beginn Ihres Codes:

#import <QuartzCore/QuartzCore.h>

Wenn Sie nun alle Animationen einer Ansicht stoppen möchten, die in ihren Spuren tot ist, sagen Sie Folgendes:

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

Die Mittellinie würde von selbst funktionieren, aber es gibt eine Verzögerung, bis der Runloop beendet ist (der "Neuzeichnungsmoment"). Um diese Verzögerung zu vermeiden, schließen Sie den Befehl wie gezeigt in einen expliziten Transaktionsblock ein. Dies funktioniert, sofern auf dieser Ebene im aktuellen Runloop keine weiteren Änderungen vorgenommen wurden.

matt
quelle
2
Ich habe [CATransaction Flush] nach Ihrem Code verwendet, was großartig funktioniert, manchmal, weil er nicht sofort schreibt. Es gibt jedoch ein Leistungsproblem von 0,1 Sekunden, z. B. bleibt es für diese bestimmte Zeit zurück. Problemumgehungen?
Sidwyn Koh
5
removeAllAnimationshat den Nebeneffekt, dass die Animation auf das endgültige Bild gebracht wird, anstatt sie dort anzuhalten, wo sie sich gerade befindet.
Ilya
3
Wenn eine Animation gestoppt werden soll, wird normalerweise erwartet, dass sie bei ihrem aktuellen Bild stoppt - weder beim ersten noch beim letzten Bild. Das Springen zum ersten oder letzten Bild sieht aus wie eine ruckartige Bewegung.
Ilya
1
Ich würde die Antwort dann detailliert beschreiben, um anzugeben, was das Standardverhalten ist und wie es geändert werden kann. Offensichtlich interessiert sich jemand, der Animation macht, aktiv dafür, was nach seinen Methodenaufrufen auf dem Bildschirm passiert :)
Ilya
1
Ich habe es an anderer Stelle detailliert. Das wurde nicht gefragt, also habe ich hier nicht geantwortet. Wenn Sie eine andere Antwort haben, geben Sie sie auf jeden Fall als Antwort!
Matt
48

VerwendenUIViewAnimationOptionBeginFromCurrentState Sie unter iOS 4 und höher die Option für die zweite Animation, um die erste Animation zu kürzen.

Angenommen, Sie haben eine Ansicht mit einem Aktivitätsindikator. Sie möchten die Aktivitätsanzeige einblenden, während eine möglicherweise zeitaufwändige Aktivität beginnt, und sie ausblenden, wenn die Aktivität beendet ist. Im folgenden Code wird die Ansicht mit der Aktivitätsanzeige aufgerufen 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
quelle
41

Um eine Animation abzubrechen, müssen Sie lediglich die Eigenschaft festlegen, die gerade animiert wird, außerhalb der UIView-Animation. Dadurch wird die Animation gestoppt, wo immer sie sich befindet, und das UIView springt zu der gerade definierten Einstellung.

Tomtaylor
quelle
9
Dies ist nur teilweise richtig. Es stoppt zwar die Animation, aber es hindert sie nicht daran, Delegatenmethoden auszulösen, insbesondere den animationComplete-Handler. Wenn Sie also Logik haben, werden Sie Probleme sehen.
M. Ryan
33
Dafür ist das 'fertige' Argument da -animationDidStop:finished:context:. Wenn ja [finished boolValue] == NO, dann wissen Sie, dass Ihre Animation irgendwie abgebrochen wurde.
Nick Forge
Dies ist ein großartiger Tipp von vor 7 Jahren! Beachten Sie, dass es ein seltsames Verhalten gibt: Angenommen, Sie animieren Alpha hin und her und lassen es auf "0" gehen. Um die Animation zu stoppen, können Sie Alpha nicht auf Null setzen. Sie setzen es auf 1.
Fattie
26

Es tut uns leid, diese Antwort wieder zu beleben, aber in iOS 10 haben sich die Dinge irgendwie geändert, und jetzt ist das Abbrechen möglich und Sie können sogar ordnungsgemäß abbrechen!

Nach iOS 10 können Sie Animationen mit UIViewPropertyAnimator abbrechen !

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

Wenn Sie true übergeben, wird die Animation abgebrochen und genau dort angehalten, wo Sie sie abgebrochen haben. Die Abschlussmethode wird nicht aufgerufen. Wenn Sie jedoch false übergeben, sind Sie für die Fertigstellung der Animation verantwortlich:

animator.finishAnimation(.start)

Sie können Ihre Animation beenden und im aktuellen Status (.current) bleiben oder in den Anfangszustand (.start) oder in den Endzustand (.end) wechseln.

Übrigens können Sie später sogar pausieren und neu starten ...

animator.pauseAnimation()
animator.startAnimation()

Hinweis: Wenn Sie kein abruptes Abbrechen wünschen, können Sie Ihre Animation umkehren oder sogar ändern, nachdem Sie sie angehalten haben!

Tiago Almeida
quelle
2
Dies ist definitiv die relevanteste für moderne Animationen.
Doug
10

Wenn Sie eine Einschränkung animieren, indem Sie die Konstante anstelle einer Ansichtseigenschaft ändern, funktioniert keine der anderen Methoden unter iOS 8.

Beispielanimation:

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

                 }];

Lösung:

Sie müssen die Animationen aus den Ebenen aller Ansichten entfernen, die von der Einschränkungsänderung betroffen sind, sowie deren Unterschichten.

[self.constraintView.layer removeAllAnimations];
for (CALayer *l in self.constraintView.layer.sublayers)
{
    [l removeAllAnimations];
}
Nikola Lajic
quelle
1
Funktioniert auch nur self.constraintView.layer.removeAllAnimations()(Swift)
Lachtan
Keine der anderen Lösungen hat funktioniert. Danke, es hat bei mir funktioniert.
Swapnilagarwal
5

Keiner der oben genannten Punkte hat es für mich gelöst, aber das hat geholfen: Die UIViewAnimation legt die Eigenschaft sofort fest und animiert sie dann. Die Animation wird gestoppt, wenn die Präsentationsebene mit dem Modell (der Set-Eigenschaft) übereinstimmt.

Ich habe mein Problem gelöst: "Ich möchte animieren, von wo aus Sie aussehen, als würden Sie erscheinen" ("Sie" bedeutet die Ansicht). Wenn Sie das wollen, dann:

  1. QuarzCore hinzufügen.
  2. CALayer * pLayer = theView.layer.presentationLayer;

Stellen Sie die Position auf die Präsentationsebene ein

Ich benutze ein paar Optionen einschließlich UIViewAnimationOptionOverrideInheritedDuration

Da die Dokumentation von Apple jedoch vage ist, weiß ich nicht, ob sie die anderen Animationen bei der Verwendung wirklich überschreibt oder nur die Timer zurücksetzt.

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

Und das sollte vorerst eine anständige Lösung sein.

NazCodezz
quelle
5
[UIView setAnimationsEnabled:NO];
// your code here
[UIView setAnimationsEnabled:YES];
ideawu
quelle
2

Schnelle Version von Stephen Darlingtons Lösung

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

// set view properties
UIView.commitAnimations()
lucamegh
quelle
1

Ich habe das gleiche Problem; Die APIs haben nichts, um eine bestimmte Animation abzubrechen. Das

+ (void)setAnimationsEnabled:(BOOL)enabled

Deaktiviert ALLE Animationen und funktioniert daher bei mir nicht. Es gibt zwei Lösungen:

1) Machen Sie Ihr animiertes Objekt zu einer Unteransicht. Wenn Sie dann die Animationen für diese Ansicht abbrechen möchten, entfernen Sie die Ansicht oder blenden Sie sie aus. Sehr einfach, aber Sie müssen die Unteransicht ohne Animationen neu erstellen, wenn Sie sie im Blick behalten möchten.

2) Wiederholen Sie die Animation nur einmal und wählen Sie einen Delegierten aus, um die Animation bei Bedarf neu zu starten:

-(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 mit 2) ist, dass es immer eine Verzögerung gibt, die Animation zu stoppen, wenn der aktuelle Animationszyklus beendet ist.

Hoffe das hilft.


quelle
1

Auch wenn Sie die Animation wie oben beschrieben abbrechen, wird die Animation didStopSelectorweiterhin ausgeführt. Wenn Ihre Anwendung logische Zustände enthält, die von Animationen gesteuert werden, treten Probleme auf. Aus diesem Grund verwende ich mit den oben beschriebenen Methoden die Kontextvariable von UIViewAnimationen. Wenn Sie den aktuellen Status Ihres Programms über den Kontextparameter an die Animation übergeben, didStopSelectorkann Ihre Funktion beim Stoppen der Animation entscheiden, ob sie etwas tun oder nur basierend auf dem aktuellen Status und dem als Kontext übergebenen Statuswert zurückkehren soll.

Gorki
quelle
NickForge erklärt dies perfekt in dem Kommentar unter TomTaylors korrekter Antwort oben ...
Fattie
Vielen Dank für diesen Hinweis! Ich habe in der Tat eine solche Situation, in der eine nächste Animation ausgelöst wird, wenn animationDidStop aufgerufen wird. Wenn Sie einfach eine Animation entfernen und sofort eine neue für denselben Schlüssel hinzufügen, funktioniert dies meiner Meinung nach nicht. Sie müssen beispielsweise warten, bis animationDidStop für eine andere Animation oder etwas anderes ausgelöst wird. Die gerade entfernte Animation löst nicht mehr animationDidStop aus.
RickJansen
0
CALayer * pLayer = self.layer.presentationLayer;
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView animateWithDuration:0.001 animations:^{
    self.frame = pLayer.frame;
}];
Donnerkaninchen
quelle
0

So halten Sie eine Animation an, ohne den Status auf das Original oder das Finale zurückzusetzen:

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

Wenn ich mit UIStackViewAnimation arbeite , muss removeAllAnimations()ich außerdem einige Werte auf den Anfangswert setzen, da removeAllAnimations()sie in einen unvorhersehbaren Zustand versetzt werden können. Ich habe stackViewmit view1und view2drinnen, und eine Ansicht sollte sichtbar und eine verborgen sein:

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.
quelle
0

Keine der beantworteten Lösungen hat bei mir funktioniert. Ich habe meine Probleme auf diese Weise gelöst (ich weiß nicht, ob es der richtige Weg ist?), Weil ich Probleme hatte, als ich dies zu schnell aufrief (als die vorherige Animation noch nicht fertig war). Ich übergebe meine gewünschte Animation mit dem customAnim- Block.

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
quelle