Animation Callback für CALayer beenden?

70

Ich frage mich, wo die Rückrufe für Animationen in einem CALayer sind (oder ob es irgendetwas gibt). Insbesondere für implizite Animationen wie das Ändern des Rahmens, der Position usw. In einer UIView können Sie Folgendes tun:

[UIView beginAnimations:@"SlideOut" context:nil];
[UIView setAnimationDuration:.3];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(animateOut:finished:context:)];
CGRect frame = self.frame;
frame.origin.y = 480;
self.frame = frame;
[UIView commitAnimations];

Insbesondere ist dies das, setAnimationDidStopSelectorwas ich für eine Animation in einem CALayer möchte. Gibt es so etwas?

TIA.

Jeffrey Forbes
quelle
Für jeden, der hier googelt, habe ich eine moderne Antwort auf diese unglaublich alte Frage gegeben! : O Suche bis "2017 ..."
Fattie

Antworten:

132

Sie können eine CATransaction verwenden, die über einen Abschlussblock-Handler verfügt.

[CATransaction begin];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
[pathAnimation setDuration:1];
[pathAnimation setFromValue:[NSNumber numberWithFloat:0.0f]];    
[pathAnimation setToValue:[NSNumber numberWithFloat:1.0f]];
[CATransaction setCompletionBlock:^{_lastPoint = _currentPoint; _currentPoint = CGPointMake(_lastPoint.x + _wormStepHorizontalValue, _wormStepVerticalValue);}];
[_pathLayer addAnimation:pathAnimation forKey:@"strokeEnd"];
[CATransaction commit];
bennythemink
quelle
32
Beachten Sie, dass es wichtig ist, den Abschlussblock festzulegen, bevor Sie die Animation zur Ebene hinzufügen.
Groot
1
Dies ist nicht immer nützlich, da Sie auch dann aufgerufen werden, wenn Sie die Animation entfernen, auch wenn sie nicht beendet wurde.
Yuval Tal
66

Ich habe meine eigene Frage beantwortet. Sie müssen eine Animation CABasicAnimationwie folgt hinzufügen :

CABasicAnimation* anim = [CABasicAnimation animationWithKeyPath:@"frame"];
anim.fromValue = [NSValue valueWithCGRect:layer.frame];
anim.toValue = [NSValue valueWithCGRect:frame];
anim.delegate = self;
[layer addAnimation:anim forKey:@"frame"];

Wenn Sie die Delegate-Methode implementieren animationDidStop:finished:, sollten Sie bereit sein. Gott sei Dank existiert diese Funktionalität! : D.

Jeffrey Forbes
quelle
3
Diese Antwort ist nützlich für die Informationen über Delegate, hat jedoch einen großen Fehler: Sie können "Frame" nicht animieren, da es sich um eine abgeleitete Eigenschaft handelt. developer.apple.com/library/mac/#qa/qa2008/qa1620.html
Michal
Sie können 'frame' nicht bearbeiten, aber es ist genauso einfach, die Mitte des Frames zu ermitteln und die Position zu animieren, zum Beispiel -> let theAnimation = CABasicAnimation (keyPath: "position"); und dann, um den Rahmenmittelpunkt zu erhalten -> theAnimation.fromValue = NSValue (cgPoint: CGPoint (x: rightImage.frame.origin.x + rightImage.frame.width / 2.0, y: rightImage.frame.origin.y + rightImage) .frame.height / 2.0))
Adam Freeman
Bitte beachten Sie, dass diese 10 Jahre alte Antwort wirklich nicht der richtige Weg ist. (Das gilt für viele alte Antworten auf SO!)
Fattie
@ Fattie - und warum sollte das so sein? Gibt es Verfallserklärungen, die ich verpasst habe?
Silverdr
54

Hier ist eine Antwort in Swift 3.0, die auf der Lösung von bennythemink basiert:

    // Begin the transaction
    CATransaction.begin()
    let animation = CABasicAnimation(keyPath: "strokeEnd")
    animation.duration = duration //duration is the number of seconds
    animation.fromValue = 0
    animation.toValue = 1
    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
    circleLayer.strokeEnd = 1.0

    // Callback function
    CATransaction.setCompletionBlock { 
        print("end animation")
    }

    // Do the actual animation and commit the transaction
    circleLayer.add(animation, forKey: "animateCircle")
    CATransaction.commit() 
tdon
quelle
52

Für 2018 ...

Einfacher geht es nicht.

Vergiss das nicht [weak self]oder du wirst abstürzen.

func animeExample() {

    CATransaction.begin()

    let a = CABasicAnimation(keyPath: "fillColor")
    a.fromValue, duration = ... etc etc

    CATransaction.setCompletionBlock{ [weak self] in
        self?.animeExample()
        self?.ringBell()
        print("again...")
    }

    someLayer.add(a, forKey: nil)
    CATransaction.commit()
}

. . . . . HINWEIS - KRITISCHER TIPP >>>>>

Sehr wichtig!

Sie MÜSSEN die Linie setCompletionBlock haben VOR der Linie someLayer.add.

Die Reihenfolge ist kritisch! Es ist eine iOS-Eigenart.

Im Beispiel ruft es sich einfach erneut auf.

Natürlich können Sie jede Funktion aufrufen.


Hinweise für alle, die noch keine Erfahrung mit iOS-Animationen haben:

  1. Der "Schlüssel" (wie in forKey) ist irrelevant und wird selten verwendet . Setzen Sie es auf Null. Wenn Sie es einstellen möchten, setzen Sie es auf "beliebige Zeichenfolge".

  2. Der "keyPath" ist in der Tat das eigentliche "Ding, das Sie animieren" . Es ist buchstäblich eine Eigenschaft der Ebene wie "Deckkraft", "Hintergrundfarbe" usw., aber als Zeichenfolge geschrieben . (Sie können dort nicht einfach "alles, was Sie wollen" eingeben, es muss der Name einer tatsächlichen Eigenschaft der Ebene sein und es muss animierbar sein.)

Um es zu wiederholen: Der "Schlüssel" (selten verwendet - setzen Sie ihn einfach auf Null) und der "Schlüsselpfad" sind völlig unabhängig voneinander.

Sie sehen oft Beispielcode, in dem diese beiden verwechselt werden (dank der albernen Benennung), was alle möglichen Probleme verursacht.


Beachten Sie, dass Sie alternativ den Delegaten verwenden können, es jedoch viel einfacher ist, nur den Abschlussblock zu verwenden, da (A) er in sich geschlossen ist und überall verwendet werden kann und (B) Sie normalerweise mehr als einen Anime haben. In diesem Fall verwenden Sie den Delegierter ist langweilig.

Fattie
quelle
Tolle Antwort Fattie - danke! Würde es Ihnen etwas ausmachen, den [weak self]Teil im Detail zu erklären ? Bei mir funktioniert es auch ohne.
Ixany
hi @ixany - wenn du kein schwaches Selbst verwendest, stürzt es ab, wenn die Ansicht während der Animation verschwindet. Dies ist eine Grundvoraussetzung für die iOS-Entwicklung, und Sie können hier viele QS darüber lesen! Genießen.
Fattie
50

4 Stunden mit diesem Müll verschwendet, nur um ein Einblenden zu machen. Beachten Sie den Kommentar im Code.

   [CATransaction begin];
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    animation.duration = 0.3;
    animation.fromValue = [NSNumber numberWithFloat:0.0f];
    animation.toValue = [NSNumber numberWithFloat:1.0f];
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeBoth;
  ///  [box addAnimation:animation forKey:@"j"]; Animation will not work if added here. Need to add this only after the completion block.

    [CATransaction setCompletionBlock:^{

        CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"opacity"];
        animation2.duration = 0.3;
        animation2.beginTime = CACurrentMediaTime()+1;
        animation2.fromValue = [NSNumber numberWithFloat:1.0f];
        animation2.toValue = [NSNumber numberWithFloat:0.0f];
        animation2.removedOnCompletion = NO;
        animation2.fillMode = kCAFillModeBoth;
        [box addAnimation:animation2 forKey:@"k"];

    }];

    [box addAnimation:animation forKey:@"j"];

    [CATransaction commit];
user3077725
quelle
Ich denke, dass Sie in Ihrer Sache die Autoreverse-Eigenschaft hätten verwenden können, weil Sie im Grunde die erste Animation umkehren.
denis631
1
Diese Antwort brachte mich zum Lachen! Ich war total dort und habe mehrere Stunden lang versucht, ein scheinbar kleines Ding zum Arbeiten zu bringen ... ✊
jason z
Sie sagen, Sie sollten animation.removedOnCompletion = NO;stattdessen nicht den Wert Ihrer Ebene festlegen, bevor Sie die Animation starten. Auf diese Weise bringen Sie Präsentation und Modellschicht durcheinander.
Martin Berger
12

Nur ein Hinweis für diejenigen, die diese Seite bei Google finden: Sie können die Aufgabe wirklich erledigen, indem Sie die Eigenschaft "delegieren" Ihres Animationsobjekts auf das Objekt setzen, das die Benachrichtigung erhält, und die Methode "animationDidStop" in der .m-Datei dieses Objekts implementieren Datei. Ich habe es gerade versucht und es funktioniert. Ich weiß nicht, warum Joe Blow gesagt hat, dass das nicht der richtige Weg ist.

moonman239
quelle
Versuchte alles von removeAllAnimations bis zu bestimmten CAAnimations mit setCompletionBlock und CATransation.begin und Commit wie die Antwort von user3077725. Versucht UIView.animateWIthDuration. Funktionierte nicht für eine einfache Alpha und position.x Animation. Die Delegatenmethode war das einzige, was richtig funktionierte
KorinW
11

In Swift 4+ habe ich gerade delegateals hinzugefügt

class CircleView: UIView,CAAnimationDelegate {
...

let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.delegate = self//Set delegate

Rückruf zum Abschluss der Animation -

func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
     print("Animation END")
  }
Jack
quelle
7

Swift 5.0

func blinkShadow(completion: @escaping (() -> Void)) {
    CATransaction.begin()
    let animation = CABasicAnimation(keyPath: "shadowRadius")
    animation.fromValue = layer.shadowRadius
    animation.toValue = 0.0
    animation.duration = 0.1
    animation.autoreverses = true
    CATransaction.setCompletionBlock(completion)
    layer.add(animation, forKey: nil)
    CATransaction.commit()
}
Jimmy_m
quelle
Dies funktionierte sehr gut für mich, aber ich musste Folgendes verwenden: CATransaction.setCompletionBlock (Vervollständigung) in Swift 5 und außerdem sicherstellen, dass: animation.fillMode = .forwards animation.isRemovedOnCompletion = false festgelegt wurde, um sicherzustellen, dass alle Animationen miteinander verkettet wurden brach nicht für verkettete Gegenstände. Vielen Dank!
Kirikintha
0

Sie können den Namen einer bestimmten Animation festlegen, wenn Sie das CAAnimation-Objekt einrichten. Vergleichen Sie in animationDiStop: beendet einfach den Namen des bereitgestellten Animationsobjekts, um bestimmte Funktionen basierend auf der Animation auszuführen.

JavaJoe
quelle
3
Es gibt keine Namenseigenschaft?
Pronebird
0

Für 2020 ...

ValueAnimator, aktualisieren Sie Ihre benutzerdefinierten Eigenschaften.

https://github.com/Only-IceSoul/ios-jjvalueanimator

 class OnAnimationListener : AnimatorListener {

        weak var s : ViewController?

        init(_ ins: ViewController) {
            s = ins
        }
        func onAnimationStart(_ animation: Animator) {}
        func onAnimationEnd(_ animation: Animator) {

           print("end")
           s?.label.text = "end"

        }
        func onAnimationCancel(_ animation: Animator) {}
        func onAnimationRepeat(_ animation: Animator) {}

    }
IceSoul
quelle
0

Ich habe eine Erweiterung zu CAAnimation geschrieben, die Ihnen einen Anfang und einen Abschluss bietet, da ich es satt hatte, den Delegaten zu implementieren, insbesondere für mehrere Animationen, bei denen Sie schreckliche Dinge tun müssten, z. B. den Schlüssel der Animation verwenden, um zu sehen, welche Animation vorhanden ist den Delegierten anrufen - macht so etwas wirklich einfach.

Es ist auf GitHub - Animationsaktionen

Hoffentlich nützlich für jemanden!

SomaMan
quelle