How can I get a CAAnimation to call a block every animation tick?

我的梦境 提交于 2020-01-01 11:45:30

问题


Can I somehow have a block execute on every "tick" of a CAAnimation? It could possibly work like the code below. If there's a way to do this with a selector, that works too.

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:


The typical technique is to employ a display link (CADisplayLink).

So, define a property for the display link:

@property (nonatomic, strong) CADisplayLink *displayLink;

And then implement the methods to start, stop, and handle the 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));
}

Note, while a view is animating, you cannot look at its basic properties (because it will reflect the end destination rather than the "in flight" position), but rather you access the animated view's layer's presentationLayer, as shown above.

Anyway, start the display link when you start your animation. And remove it upon completion.

With standard block-based animation, you'd do something like:

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

[self startDisplayLink];

With Core Animation, it might look like:

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



回答2:


Using a CADisplayLink to achieve this is ideal, but using it directly requires quite a bit of code.

Whether you're working at the Core Animation level or UIView animation level, take a look at the open source INTUAnimationEngine library. It provides a very simple API that is almost exactly like the UIView block-based API, but instead it runs the animations block each frame, passing in a percentage complete:

// INTUAnimationEngine.h

// ...

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

// ...

If you simply call this method at the same time when you start your other animations and use the same duration & delay values, you'll be able to receive a callback at each frame of the animation. If you don't like duplicating code, INTUAnimationEngine can be used exclusively to run your animation as well, with advanced features like support for custom easing functions.



来源:https://stackoverflow.com/questions/22727318/how-can-i-get-a-caanimation-to-call-a-block-every-animation-tick

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!