Как определить CAAnimation в делегате animationDidStop?
у меня была проблема, когда у меня была серия перекрывающихся последовательностей CATransition / CAAnimation, все из которых мне нужно было выполнять пользовательские операции при остановке анимации, но мне нужен был только один обработчик делегата для animationDidStop.
однако у меня была проблема, не было способа однозначно идентифицировать каждую CATransition / CAAnimation в делегате animationDidStop.
Я решил эту проблему с помощью системы ключей / значений, представленной как часть CAAnimation.
при запуске анимации используйте метод setValue на CATransition / CAAnimation для установки идентификаторов и значений, которые будут использоваться при срабатывании animationDidStop:
-(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];
}
в вашем делегате animationDidStop:
- (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.
}
другой аспект этого заключается в том, что он позволяет вам сохранять состояние в системе сопряжения ключевых значений вместо того, чтобы хранить его в своем классе делегатов. Чем меньше кода, тем лучше.
будьте уверены, чтобы проверить ссылка Apple на кодирование пары ключевых значений.
существуют ли лучшие методы для идентификации CAAnimation / CATransition в делегате animationDidStop?
спасибо,
-- Батгар
10 ответов:
техника Батгара слишком сложна. Почему бы не воспользоваться параметром forKey в addAnimation? Он был предназначен именно для этой цели. Просто выньте вызов setValue и переместите строку ключа в вызов addAnimation. Например:
[[hearingAidHalo layer] addAnimation:animation forKey:@"Throb"];затем, в вашем animationDidStop обратного вызова, вы можете сделать что-то вроде:
if (theAnimation == [[hearingAidHalo layer] animationForKey:@"Throb"]) ...
Я только что придумал еще лучший способ сделать код завершения для CAAnimations:
Я создал typedef для блок:
typedef void (^animationCompletionBlock)(void);и ключ, который я использую, чтобы добавить блок к анимации:
#define kAnimationCompletionBlock @"animationCompletionBlock"затем, если я хочу запустить код завершения анимации после завершения CAAnimation, я устанавливаю себя в качестве делегата анимации и добавляю блок кода в анимацию с помощью setValue:forKey:
animationCompletionBlock theBlock = ^void(void) { //Code to execute after the animation completes goes here }; [theAnimation setValue: theBlock forKey: kAnimationCompletionBlock];затем я реализую animationDidStop: finished: метод, который проверяет наличие блока по указанному ключу и выполняет его при обнаружении:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag { animationCompletionBlock theBlock = [theAnimation valueForKey: kAnimationCompletionBlock]; if (theBlock) theBlock(); }красота этого подхода заключается в том, что вы можете написать код очистки в том же месте, где вы создаете объект анимации. Еще лучше, так как код является блоком, он имеет доступ к локальным переменным в охватывающей области, в которой он определен. Вам не нужно возиться с настройкой словарей userInfo или другой подобной ерундой, и не нужно писать постоянно растущий animationDidStop: finished: метод, который становится все более и более сложным по мере добавления различных видов анимации.
по правде говоря, CAAnimation должен иметь свойство блока завершения, встроенное в него, и системную поддержку для автоматического вызова его, если он указан. Тем не менее, приведенный выше код дает вам ту же функциональность только с несколькими строками дополнительного кода.
второй подход будет работать только в том случае, если вы явно установите анимацию в не быть удалены по завершении перед запуском его:
CAAnimation *anim = ... anim.removedOnCompletion = NO;Если вы не сделаете этого, ваша анимация будет удалена до того, как она завершится, и обратный вызов не найдет ее в словаре.
все остальные ответы слишком сложны! Почему бы вам просто не добавить свой собственный ключ для идентификации анимации?
Это решение очень легко все, что вам нужно, это добавить свой собственный ключ к анимации (animationID в этом примере)
вставьте эту строку для идентификации animation1:
[myAnimation1 setValue:@"animation1" forKey:@"animationID"];и это для идентификации animation2:
[myAnimation2 setValue:@"animation2" forKey:@"animationID"];проверьте это такой:
- (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 } }это не требуется никаких переменных экземпляра:
чтобы сделать явным то, что подразумевается сверху (и что привело меня сюда после нескольких потерянных часов): не ожидайте увидеть исходный объект анимации, который вы выделили, переданный вам
- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)flagкогда анимация заканчивается, потому что
[CALayer addAnimation:forKey:]создает копию вашей анимации.на что вы можете положиться, так это на то, что ключевые значения, которые вы дали своему объекту анимации, все еще существуют с эквивалентным значением (но не обязательно эквивалентностью указателя) в реплике анимация объекта передается с помощью
animationDidStop:finished:сообщение. Как упоминалось выше, используйте KVC, и вы получите достаточную область для хранения и извлечения состояния.
Я вижу в основном ответы objc я сделаю один для swift 2.3 на основе лучшего ответа выше.
для начала будет хорошо хранить все эти ключи на частной структуре, так что это типобезопасно и изменение его в будущем не принесет вам раздражающих ошибок только потому, что вы забыли изменить его везде в коде:
private struct AnimationKeys { static let animationType = "animationType" static let volumeControl = "volumeControl" static let throbUp = "throbUp" }как вы можете видеть, я изменил имена переменных/анимации, так это более понятно. Теперь устанавливаем эти клавиши, когда анимация есть создан.
volumeControlAnimation.setValue(AnimationKeys.volumeControl, forKey: AnimationKeys.animationType)(...)
throbUpAnimation.setValue(AnimationKeys.throbUp, forKey: AnimationKeys.animationType)затем, наконец, обработка делегата, когда анимация останавливается
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 } } }
IMHO использование ключевого значения Apple-это элегантный способ сделать это: он специально предназначен для добавления конкретных данных приложения к объектам.
другая гораздо менее элегантная возможность-хранить ссылки на ваши объекты анимации и выполнять сравнение указателей для их идентификации.
для меня, чтобы проверить, если 2 объекта CABasicAnimation являются одной и той же анимацией, Я использую функцию keyPath, чтобы сделать именно так.
if ([animationA keyPath] = = [animationB keyPath])
- нет необходимости устанавливать KeyPath для CABasicAnimation, поскольку он больше не будет анимировать
Xcode 9 Swift 4.0
вы можете использовать ключевые значения, чтобы связать анимацию, добавленную к анимации, возвращенной в методе делегата animationDidStop.
объявите словарь, содержащий все активные анимации и связанные с ними дополнения:
var animationId: Int = 1 var animating: [Int : () -> Void] = [:]когда вы добавляете свою анимацию, установите для нее ключ:
moveAndResizeAnimation.setValue(animationId, forKey: "CompletionId") animating[animationId] = { print("completion of moveAndResize animation") } animationId += 1В animationDidStop, происходит волшебство:
let animObject = anim as NSObject if let keyValue = animObject.value(forKey: "CompletionId") as? Int { if let completion = animating.removeValue(forKey: keyValue) { completion() } }
мне нравится использовать
setValue:forKey: чтобы сохранить ссылку на вид, который я анимирую, это более безопасно, чем пытаться однозначно идентифицировать анимацию на основе ID, потому что один и тот же вид анимации может быть добавлен в разные слои.эти два эквивалентны:
[UIView animateWithDuration: 0.35 animations: ^{ myLabel.alpha = 0; } completion: ^(BOOL finished) { [myLabel removeFromSuperview]; }];С этим:
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;и в методе делегата:
- (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]; } }
Comments