2014-03-29 12 views
5

Kann ich irgendwie bei jedem "Tick" einer CAAnimation einen Block ausführen lassen? Es könnte möglicherweise wie der Code unten funktionieren. Wenn es eine Möglichkeit gibt, dies mit einem Selektor zu tun, funktioniert das auch.Wie kann ich eine CAAnimation veranlassen, jeden Animations-Tick einen Block aufzurufen?

NSString* keyPath = @"position.x"; 
CGFloat endValue = 100; 

[CATransaction setDisableActions:YES]; 
[self.layer setValue:@(toVal) forKeyPath:keyPath]; 

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:keyPath]; 
animation.duration = 1; 
animation.delegate = self; 

__weak SelfClass* wself = self; 
animation.eachTick = ^{ 
    NSLog(@"Current x value: %f", [wself valueForKeyPath:keypath]); 
}; 

[self.layer addAnimation:animation forKey:nil]; 
+1

BTW, ich bezweifle, dass Sie daran interessiert sind, aber in UIKit Dynamics gibt es eine 'action' Blockeigenschaft auf' UIDynamicBehavior' Objekte, die genau das tut, was Sie fragen. Aber UIKit Dynamics ist (a) nur iOS 7; und (b) ist Overkill, wenn man nur die Bewegung einer Ansicht animiert. Ich erwähne es nur wegen der auffallenden Symmetrie zwischen dem, was Sie mit Ihrem 'eachTick' Block und dem' action' Block wollten. Ich denke immer noch, dass ein 'CADisplayLink' in diesem Fall wahrscheinlich mehr Sinn macht. – Rob

Antwort

9

Die typische Technik ist die Verwendung einer Anzeigeverbindung (CADisplayLink).

Also, eine Eigenschaft für die Anzeige Link definieren:

@property (nonatomic, strong) CADisplayLink *displayLink; 

Und dann die Methoden implementieren zu starten, zu stoppen und zu handhaben, den Display-Link:

- (void)startDisplayLink 
{ 
    self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)]; 
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; 
} 

- (void)stopDisplayLink 
{ 
    [self.displayLink invalidate]; 
    self.displayLink = nil; 
} 

- (void)handleDisplayLink:(CADisplayLink *)displayLink 
{ 
    CALayer *layer = self.animatedView.layer.presentationLayer; 

    NSLog(@"%@", NSStringFromCGRect(layer.frame)); 
} 

Hinweis, während eine Ansicht ist Beim Animieren können Sie nicht die grundlegenden Eigenschaften betrachten (weil es das Endziel und nicht die Position "im Flug" widerspiegelt), sondern Sie greifen auf die Ebene der animierten Ansichtsebene zu, wie oben gezeigt.

Wie auch immer, starten Sie den Display-Link, wenn Sie Ihre Animation starten. Und entferne es nach Abschluss.

Mit Standard-Block-basierte Animation, würden Sie so etwas wie zu tun:

[UIView animateWithDuration:2.0 delay:0 options:0 animations:^{ 
    view.frame = someNewFrame; 
} completion:^(BOOL finished) { 
    [self stopDisplayLink]; 
}]; 

[self startDisplayLink]; 

mit Core Animation, es könnte wie folgt aussehen:

CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; 
animation.fromValue = [NSValue valueWithCGPoint:startPoint]; 
animation.toValue = [NSValue valueWithCGPoint:endPoint]; 

[CATransaction setAnimationDuration:1.0]; 
[CATransaction setCompletionBlock:^{ 
    [self stopDisplayLink]; 
}]; 

[CATransaction begin]; 
[self.animatedView.layer addAnimation:animation forKey:nil]; 
[CATransaction commit]; 

[self startDisplayLink]; 
+0

Sie sagen "Starten Sie den Anzeige-Link ** vor ** Ihrer Animation", aber dann hat Ihr Code '[self startDisplayLink]' ** nach ** der Animation. Entschuldigung, wenn ich hier falsch liege. – aleclarson

+0

@aleclarson Da die Animation asynchron ausgeführt wird, ist die Reihenfolge nicht besonders kritisch. Der Schlüssel ist, starten Sie den Bildschirm Link, wenn Sie Ihre Animation starten. Also habe ich meine Antwort umformuliert, um diese Verwirrung zu beseitigen. – Rob

+0

Sie waren eine große Hilfe. Danke Rob! – aleclarson

0

eine CADisplayLink Mit dies zu erreichen, ist ideal, aber es direkt zu verwenden erfordert ziemlich viel Code.

Ob Sie auf der Ebene der Core Animation oder der UIView-Animation arbeiten, werfen Sie einen Blick auf die Open Source INTUAnimationEngine library. Es bietet eine sehr einfache API, die fast genau wie der UIView Block-basierte API ist, sondern führt es das animations Block jeden Frame, in einem Prozentsatz vorbei vollständig:

// INTUAnimationEngine.h 

// ... 

+ (NSInteger)animateWithDuration:(NSTimeInterval)duration 
          delay:(NSTimeInterval)delay 
         animations:(void (^)(CGFloat percentage))animations 
         completion:(void (^)(BOOL finished))completion; 

// ... 

Wenn Sie einfach diese Methode aufrufen, auf der gleichen Zeit, wenn Sie Ihre anderen Animationen starten und die gleiche Dauer & Verzögerungswerte verwenden, können Sie in jedem Frame der Animation einen Rückruf erhalten. Wenn Sie Code nicht duplizieren möchten, kann INTUAnimationEngine ausschließlich für die Ausführung Ihrer Animation verwendet werden, mit erweiterten Funktionen wie Unterstützung für benutzerdefinierte Beschleunigungsfunktionen.