Как определить 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];
    }
}