Как я могу получить CAAnimation для вызова блока каждый тик анимации?

Могу ли я как-нибудь выполнить блок на каждом "тике" CAAnimation? Это может работать как код ниже. Если есть способ сделать это с помощью селектора, это тоже работает.

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

2 ответа

Решение

Типичный метод заключается в использовании ссылки на дисплей (CADisplayLink).

Итак, определите свойство для отображаемой ссылки:

@property (nonatomic, strong) CADisplayLink *displayLink;

А затем реализуйте методы для запуска, остановки и обработки ссылки на отображение:

- (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));
}

Обратите внимание, что пока анимация вида, вы не можете смотреть на его основные свойства (потому что он будет отражать конечный пункт назначения, а не положение "в полете"), а вместо этого вы получаете доступ к слою анимированного вида. presentationLayer, как показано выше.

В любом случае, начните отображать ссылку, когда вы начинаете анимацию. И удалите его после завершения.

Со стандартной блочной анимацией вы бы сделали что-то вроде:

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

[self startDisplayLink];

С Core Animation это может выглядеть так:

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

Использование CADisplayLink для достижения этой цели идеально, но для его непосредственного использования требуется совсем немного кода.

Работаете ли вы на уровне Core Animation или на уровне анимации UIVie w, взгляните на библиотеку INTUAnimationEngine с открытым исходным кодом. Он предоставляет очень простой API, который почти в точности похож на блочный API UIVie w, но вместо этого он запускает animations блокировать каждый кадр, передавая в процентах завершения:

// INTUAnimationEngine.h

// ...

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

// ...

Если вы просто вызываете этот метод в то же время, когда запускаете другие анимации и используете те же значения длительности и задержки, вы сможете получать обратный вызов в каждом кадре анимации. Если вам не нравится дублирование кода, INTUAnimationEngine можно использовать исключительно для запуска анимации, с расширенными функциями, такими как поддержка пользовательских функций замедления.

Другие вопросы по тегам