Wie identifiziere ich CAAnimation innerhalb des animationDidStop-Delegaten?

102

Ich hatte ein Problem mit einer Reihe überlappender CATransition / CAAnimation-Sequenzen, die ich alle ausführen musste, um benutzerdefinierte Vorgänge auszuführen, wenn die Animationen gestoppt wurden, aber ich wollte nur einen Delegaten-Handler für animationDidStop.

Ich hatte jedoch ein Problem, es schien keine Möglichkeit zu geben, jede CATransition / CAAnimation im animationDidStop-Delegaten eindeutig zu identifizieren.

Ich habe dieses Problem über das Schlüssel- / Wertesystem gelöst, das im Rahmen von CAAnimation verfügbar gemacht wurde.

Wenn Sie Ihre Animation starten, verwenden Sie die setValue-Methode in CATransition / CAAnimation, um Ihre Bezeichner und Werte festzulegen, die beim Auslösen von animationDidStop verwendet werden sollen:

-(void)volumeControlFadeToOrange
{   
    CATransition* volumeControlAnimation = [CATransition animation];
    [volumeControlAnimation setType:kCATransitionFade];
    [volumeControlAnimation setSubtype:kCATransitionFromTop];
    [volumeControlAnimation setDelegate:self];
    [volumeControlLevel setBackgroundImage:[UIImage imageNamed:@"SpecialVolume1.png"] forState:UIControlStateNormal];
    volumeControlLevel.enabled = true;
    [volumeControlAnimation setDuration:0.7];
    [volumeControlAnimation setValue:@"Special1" forKey:@"MyAnimationType"];
    [[volumeControlLevel layer] addAnimation:volumeControlAnimation forKey:nil];    
}

- (void)throbUp
{
    doThrobUp = true;

    CATransition *animation = [CATransition animation]; 
    [animation setType:kCATransitionFade];
    [animation setSubtype:kCATransitionFromTop];
    [animation setDelegate:self];
    [hearingAidHalo setBackgroundImage:[UIImage imageNamed:@"m13_grayglow.png"] forState:UIControlStateNormal];
    [animation setDuration:2.0];
    [animation setValue:@"Throb" forKey:@"MyAnimationType"];
    [[hearingAidHalo layer] addAnimation:animation forKey:nil];
}

In Ihrem animationDidStop-Delegaten:

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag{

    NSString* value = [theAnimation valueForKey:@"MyAnimationType"];
    if ([value isEqualToString:@"Throb"])
    {
       //... Your code here ...
       return;
    }


    if ([value isEqualToString:@"Special1"])
    {
       //... Your code here ...
       return;
    }

    //Add any future keyed animation operations when the animations are stopped.
 }

Der andere Aspekt ist, dass Sie den Status im Schlüsselwertpaarungssystem beibehalten können, anstatt ihn in Ihrer Delegatenklasse speichern zu müssen. Je weniger Code, desto besser.

Lesen Sie unbedingt die Apple-Referenz zur Codierung von Schlüsselwertpaaren .

Gibt es bessere Techniken für die CAAnimation / CATransition-Identifizierung im animationDidStop-Delegaten?

Danke, - Batgar

Batgar
quelle
4
Batgar, als ich nach "iphone animationDidStop identifiziere" gegoogelt habe, war der erste Treffer Ihr Beitrag, der die Verwendung von Schlüsselwerten zur Identifizierung der Animation vorschlug. Genau das, was ich brauchte, danke. Rudi
Rudifa
1
Beachten Sie, dass CAAnimation‚s delegatestark, so dass Sie es einstellen können müssen, um nilzu vermeiden Zyklen zu behalten!
Iulian Onofrei

Antworten:

92

Batgars Technik ist zu kompliziert. Nutzen Sie den forKey-Parameter in addAnimation. Es war genau für diesen Zweck gedacht. Nehmen Sie einfach den Aufruf von setValue heraus und verschieben Sie die Schlüsselzeichenfolge in den Aufruf von addAnimation. Beispielsweise:

[[hearingAidHalo layer] addAnimation:animation forKey:@"Throb"];

In Ihrem animationDidStop-Rückruf können Sie dann Folgendes tun:

if (theAnimation == [[hearingAidHalo layer] animationForKey:@"Throb"]) ...
Vocaro
quelle
Ich möchte erwähnen, dass mit den oben genannten ERHÖHUNGEN DER RETAIN COUNT! Sei gewarnt. Das heißt, animationForKey: erhöht die Anzahl der Aufbewahrungen Ihres CAAnimation-Objekts.
mmilo
1
@mmilo Das ist nicht sehr überraschend, oder? Durch Hinzufügen einer Animation zu einer Ebene besitzt die Ebene die Animation, sodass die Anzahl der beibehaltenen Animationen natürlich erhöht wird.
GorillaPatch
16
Funktioniert nicht - bis der Stopp-Selektor aufgerufen wird, ist die Animation nicht mehr vorhanden. Sie erhalten eine Nullreferenz.
Adam
4
Das ist ein Missbrauch des forKey: -Parameters, und es besteht keine Notwendigkeit dafür. Was Batgar tat, ist genau richtig - mit der Schlüsselwertcodierung können Sie beliebige Daten an Ihre Animation anhängen, um sie leicht zu identifizieren.
Matt
7
Adam, siehe Jimts Antwort unten - du musst festlegen anim.removedOnCompletion = NO;, dass es noch existiert, wenn -animationDidStop:finished:es aufgerufen wird.
Yang Meyer
46

Ich habe mir gerade einen noch besseren Weg ausgedacht, um den Abschlusscode für CAAnimations zu erstellen:

Ich habe ein typedef für einen Block erstellt:

typedef void (^animationCompletionBlock)(void);

Und ein Schlüssel, mit dem ich einer Animation einen Block hinzufüge:

#define kAnimationCompletionBlock @"animationCompletionBlock"

Wenn ich dann nach Abschluss einer CAAnimation einen Animationsabschlusscode ausführen möchte, setze ich mich als Delegat der Animation und füge der Animation mit setValue: forKey: einen Codeblock hinzu.

animationCompletionBlock theBlock = ^void(void)
{
  //Code to execute after the animation completes goes here    
};
[theAnimation setValue: theBlock forKey: kAnimationCompletionBlock];

Dann implementiere ich eine animationDidStop: finish: -Methode, die am angegebenen Schlüssel nach einem Block sucht und ihn ausführt, wenn er gefunden wird:

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
  animationCompletionBlock theBlock = [theAnimation valueForKey: kAnimationCompletionBlock];
  if (theBlock)
    theBlock();
}

Das Schöne an diesem Ansatz ist, dass Sie den Bereinigungscode an derselben Stelle schreiben können, an der Sie das Animationsobjekt erstellen. Besser noch, da der Code ein Block ist, hat er Zugriff auf lokale Variablen in dem umschließenden Bereich, in dem er definiert ist. Sie müssen sich nicht mit dem Einrichten von userInfo-Wörterbüchern oder anderem Unsinn herumschlagen und müssen keine ständig wachsende animationDidStop: finish: -Methode schreiben, die immer komplexer wird, wenn Sie verschiedene Arten von Animationen hinzufügen.

Um ehrlich zu sein, sollte in CAAnimation eine Eigenschaft für den Abschlussblock integriert sein und Systemunterstützung für den automatischen Aufruf, falls eine angegeben ist. Der obige Code bietet Ihnen jedoch dieselbe Funktionalität mit nur wenigen Zeilen zusätzlichen Codes.

Duncan C.
quelle
7
Jemand hat auch eine Kategorie für CAAnimation zusammengestellt: github.com/xissburg/CAAnimationBlocks
Jay Peyer
Das scheint nicht richtig zu sein. Sehr oft erhalte ich direkt nach dem Aufrufen ein EXEC_Err theBlock();, und ich glaube, das liegt daran, dass der Bereich des Blocks zerstört wurde.
Mahboudz
Ich benutze den Block schon eine Weile und er funktioniert VIEL besser als Apples schrecklicher "offizieller" Ansatz.
Adam
3
Ich bin mir ziemlich sicher, dass Sie diesen Block [blockieren] müssen, bevor Sie ihn als Wert für eine Eigenschaft festlegen.
Fiona Hopkins
1
Nein, Sie müssen den Block nicht kopieren.
Duncan C
33

Der zweite Ansatz funktioniert nur, wenn Sie explizit festlegen, dass Ihre Animation nach Abschluss nicht entfernt wird, bevor Sie sie ausführen:

CAAnimation *anim = ...
anim.removedOnCompletion = NO;

Wenn Sie dies nicht tun, wird Ihre Animation vor Abschluss entfernt, und der Rückruf findet sie nicht im Wörterbuch.

jimt
quelle
10
Dies sollte ein Kommentar sein, keine Antwort.
Bis zum
2
Ich frage mich, ob es notwendig ist, es anschließend explizit mit removeAnimationForKey zu entfernen.
Bompf
Es kommt wirklich darauf an, was Sie tun möchten. Sie können es bei Bedarf entfernen oder lassen, weil Sie etwas anderes im Tandem ausführen möchten.
Applejack42
31

Alle anderen Antworten sind viel zu kompliziert! Warum fügen Sie nicht einfach Ihren eigenen Schlüssel hinzu, um die Animation zu identifizieren?

Diese Lösung ist sehr einfach. Sie müssen lediglich einen eigenen Schlüssel zur Animation hinzufügen (in diesem Beispiel animationID).

Fügen Sie diese Zeile ein, um die Animation1 zu identifizieren :

[myAnimation1 setValue:@"animation1" forKey:@"animationID"];

und dies, um animation2 zu identifizieren :

[myAnimation2 setValue:@"animation2" forKey:@"animationID"];

Testen Sie es so:

- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)flag
{
    if([[animation valueForKey:@"animationID"] isEqual:@"animation1"]) {
    //animation is animation1

    } else if([[animation valueForKey:@"animationID"] isEqual:@"animation2"]) {
    //animation is animation2

    } else {
    //something else
    }
}

Es sind keine Instanzvariablen erforderlich :

Tibidabo
quelle
Ich [animation valueForKey:@"animationID"]
erhalte
14

Um zu verdeutlichen, was von oben impliziert wird (und was mich nach ein paar verschwendeten Stunden hierher gebracht hat): Erwarten Sie nicht, dass das ursprüngliche Animationsobjekt, das Sie zugewiesen haben, an Sie zurückgegeben wird

 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)flag 

Wenn die Animation beendet ist, [CALayer addAnimation:forKey:]wird eine Kopie Ihrer Animation erstellt.

Sie können sich darauf verlassen, dass die Schlüsselwerte, die Sie Ihrem Animationsobjekt gegeben haben, im Replikat-Animationsobjekt, das mit der animationDidStop:finished:Nachricht übergeben wurde, immer noch einen äquivalenten Wert (aber nicht unbedingt eine Zeigeräquivalenz) aufweisen . Wie oben erwähnt, verwenden Sie KVC und Sie erhalten ausreichend Spielraum zum Speichern und Abrufen des Status.

zuerst
quelle
1
+1 Dies ist die beste Lösung! Sie können den 'Namen' der Animation mit [animation setValue:@"myanim" forKey:@"name"]festlegen und sogar die Ebene festlegen, mit der animiert werden soll [animation setValue:layer forKey:@"layer"]. Diese Werte können dann innerhalb der Delegatmethoden abgerufen werden.
Trojanfoe
valueForKey:kehrt nilfür mich zurück, eine Idee warum?
Iulian Onofrei
@IulianOnofrei Überprüfen Sie, ob Ihre Animation nicht durch eine andere Animation für dieselbe Eigenschaft verschoben wurde - dies kann als unerwarteter Nebeneffekt auftreten.
Erster
@ t0rst, Entschuldigung, da ich mehrere Animationen hatte und Copy Paste verwendete, habe ich unterschiedliche Werte für dieselbe Animationsvariable festgelegt.
Iulian Onofrei
2

Ich kann meistens objc Antworten sehen, die ich für Swift 2.3 basierend auf der besten Antwort oben machen werde.

Zunächst einmal ist es gut, alle diese Schlüssel in einer privaten Struktur zu speichern, damit sie typsicher ist. Wenn Sie sie in Zukunft ändern, werden Sie keine nervigen Fehler bekommen, nur weil Sie vergessen haben, sie überall im Code zu ändern:

private struct AnimationKeys {
    static let animationType = "animationType"
    static let volumeControl = "volumeControl"
    static let throbUp = "throbUp"
}

Wie Sie sehen können, habe ich die Namen der Variablen / Animationen geändert, damit es klarer wird. Setzen Sie nun diese Tasten, wenn die Animation erstellt wird.

volumeControlAnimation.setValue(AnimationKeys.volumeControl, forKey: AnimationKeys.animationType)

(...)

throbUpAnimation.setValue(AnimationKeys.throbUp, forKey: AnimationKeys.animationType)

Behandeln Sie dann endlich den Delegaten, wenn die Animation stoppt

override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
    if let value = anim.valueForKey(AnimationKeys.animationType) as? String {
        if value == AnimationKeys.volumeControl {
            //Do volumeControl handling
        } else if value == AnimationKeys.throbUp {
            //Do throbUp handling
        }
    }
}
Apinho
quelle
0

IMHO mit Apples Schlüsselwert ist die elegante Art, dies zu tun: Es ist speziell dazu gedacht, anwendungsspezifische Daten zu Objekten hinzuzufügen.

Eine andere, viel weniger elegante Möglichkeit besteht darin, Verweise auf Ihre Animationsobjekte zu speichern und einen Zeigervergleich durchzuführen, um sie zu identifizieren.

Teemu Kurppa
quelle
Dies wird niemals funktionieren - Sie können keine Zeigeräquivalenz durchführen, da Apple den Zeiger ändert.
Adam
0

Damit ich überprüfen kann, ob 2 CABasicAnimation-Objekte dieselbe Animation sind, verwende ich die keyPath-Funktion, um genau das zu tun.

if ([animationA keyPath] == [animationB keyPath])

  • Es ist nicht erforderlich, KeyPath für CABasicAnimation festzulegen, da es nicht mehr animiert wird
Sirisilp Kongsilp
quelle
Die Frage bezieht sich auf delegierte Rückrufe, und keyPath ist keine Methode für CAAnimation
Max MacLeod
0

Ich verwende gerne setValue:forKey: Um einen Verweis auf die Ansicht zu behalten, die ich animiere, ist es sicherer, als zu versuchen, die Animation anhand der ID eindeutig zu identifizieren, da dieselbe Art von Animation verschiedenen Ebenen hinzugefügt werden kann.

Diese beiden sind gleichwertig:

[UIView animateWithDuration: 0.35
                 animations: ^{
                     myLabel.alpha = 0;
                 } completion: ^(BOOL finished) {
                     [myLabel removeFromSuperview];
                 }];

mit diesem:

CABasicAnimation *fadeOut = [CABasicAnimation animationWithKeyPath:@"opacity"];
fadeOut.fromValue = @([myLabel.layer opacity]);
fadeOut.toValue = @(0.0);
fadeOut.duration = 0.35;
fadeOut.fillMode = kCAFillModeForwards;
[fadeOut setValue:myLabel forKey:@"item"]; // Keep a reference to myLabel
fadeOut.delegate = self;
[myLabel.layer addAnimation:fadeOut forKey:@"fadeOut"];
myLabel.layer.opacity = 0;

und in der Delegate-Methode:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    id item = [anim valueForKey:@"item"];

    if ([item isKindOfClass:[UIView class]])
    {
        // Here you can identify the view by tag, class type 
        // or simply compare it with a member object

        [(UIView *)item removeFromSuperview];
    }
}
Andrei Marincas
quelle
0

Xcode 9 Swift 4.0

Mithilfe von Schlüsselwerten können Sie eine Animation verknüpfen, die Sie der in der delegate-Methode animationDidStop zurückgegebenen Animation hinzugefügt haben.

Deklarieren Sie ein Wörterbuch, das alle aktiven Animationen und zugehörigen Vervollständigungen enthält:

 var animationId: Int = 1
 var animating: [Int : () -> Void] = [:]

Wenn Sie Ihre Animation hinzufügen, legen Sie einen Schlüssel dafür fest:

moveAndResizeAnimation.setValue(animationId, forKey: "CompletionId")
animating[animationId] = {
    print("completion of moveAndResize animation")
}
animationId += 1    

In animationDidStop geschieht die Magie:

    let animObject = anim as NSObject
    if let keyValue = animObject.value(forKey: "CompletionId") as? Int {
        if let completion = animating.removeValue(forKey: keyValue) {
            completion()
        }
    }
Eng Eibe
quelle