Как определить CAAnimation в делегате animationDidStop?
У меня возникла проблема, когда у меня была серия перекрывающихся последовательностей CATransition/CAAnimation, все из которых мне требовались для выполнения пользовательских операций при остановке анимации, но мне нужен только один обработчик делегатов для анимацииDidStop.
Однако у меня была проблема, похоже, не было способа однозначно идентифицировать каждое CATransition/CAAnimation в делегате animationDidStop.
Я решил эту проблему через систему key/value, представленную как часть CAAnimation.
При запуске анимации используйте метод setValue в CATransition/CAAnimation для установки ваших идентификаторов и значений, которые будут использоваться при анимации анимации:
-(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?
Спасибо,
--Batgar
Ответы
Ответ 1
Техника Батгага слишком сложна. Почему бы не воспользоваться параметром forKey в addAnimation? Он предназначался именно для этой цели. Просто выньте вызов setValue и переместите цепочку клавиш на вызов addAnimation. Например:
[[hearingAidHalo layer] addAnimation:animation forKey:@"Throb"];
Затем в обратном вызове animationDidStop вы можете сделать что-то вроде:
if (theAnimation == [[hearingAidHalo layer] animationForKey:@"Throb"]) ...
Ответ 2
Я просто придумал еще лучший способ сделать код завершения для 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: method, который проверяет блок по указанному ключу и выполняет его, если найден:
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
animationCompletionBlock theBlock = [theAnimation valueForKey: kAnimationCompletionBlock];
if (theBlock)
theBlock();
}
Красота такого подхода заключается в том, что вы можете написать код очистки в том же месте, где вы создаете объект анимации. Тем не менее, поскольку код является блоком, он имеет доступ к локальным переменным в охватывающей области, в которой он определен. Вам не нужно возиться с настройкой словарей userInfo или другой такой глупости и не нужно писать постоянно растущий метод анимации: Ended: finished: method, который становится все более сложным, когда вы добавляете разные виды анимаций.
По правде говоря, CAAnimation должен иметь встроенное в него свойство блока завершения и поддержку системы для автоматического его вызова, если он указан. Тем не менее, приведенный выше код дает вам такую же функциональность только с несколькими строками дополнительного кода.
Ответ 3
Второй подход будет работать только в том случае, если вы явно не установили анимацию, которая не будет удалена после ее завершения:
CAAnimation *anim = ...
anim.removedOnCompletion = NO;
Если вы этого не сделаете, ваша анимация будет удалена до того, когда она будет завершена, а обратный вызов не найдет ее в словаре.
Ответ 4
Все остальные ответы слишком сложны! Почему бы вам просто не добавить свой собственный ключ для идентификации анимации?
Это решение очень просто, вам нужно добавить свой собственный ключ в анимацию (animationID в этом примере)
Вставьте эту строку, чтобы определить animation1:
[myAnimation1 setValue:@"animation1" forKey:@"animationID"];
и для определения анимации2:
[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
}
}
Он не требует каких-либо переменных экземпляра:
Ответ 5
Чтобы сделать явное, что подразумевалось выше (и что привело меня сюда после нескольких потраченных впустую часов): не ожидайте, что исходный объект анимации, который вы выделили, передал вам
- (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)flag
когда анимация заканчивается, потому что [CALayer addAnimation:forKey:]
создает копию вашей анимации.
То, на что вы можете положиться, состоит в том, что ключевые значения, которые вы предоставили вашему объекту анимации, все еще существуют с эквивалентным значением (но не обязательно эквивалентом указателя) в объекте анимации реплики, переданном с сообщением animationDidStop:finished:
. Как упоминалось выше, используйте KVC, и вы получаете достаточную область для хранения и получения состояния.
Ответ 6
ИМХО с использованием ключевого значения Apple - это элегантный способ сделать это: он специально предназначен для добавления к конкретным данным приложений для объектов.
Другим гораздо менее элегантным вариантом является сохранение ссылок на ваши объекты анимации и сопоставление указателей для их идентификации.
Ответ 7
Чтобы проверить, является ли объект CABasicAnimation одной и той же анимацией,
Я использую функцию keyPath, чтобы делать именно это.
if ([animationA keyPath] == [анимацияB keyPath])
- Нет необходимости устанавливать KeyPath для CABasicAnimation, поскольку он больше не будет анимировать
Ответ 8
Я могу видеть в основном ответы 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
}
}
}
Ответ 9
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()
}
}
Ответ 10
Мне нравится использовать setValue:forKey
: сохранить ссылку на представление, которое я анимирую, это более безопасно, чем пытаться однозначно идентифицировать анимацию на основе идентификатора, потому что один и тот же вид анимации может быть добавлен в разные слои.
Эти два эквивалента:
[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];
}
}