Как определить CAAnimation в делегате animationDidStop? - PullRequest
97 голосов
/ 10 августа 2009

У меня была проблема, когда у меня был ряд перекрывающихся последовательностей 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?

Спасибо, --Batgar

Ответы [ 10 ]

89 голосов
/ 08 сентября 2009

Техника Бэтгара слишком сложна. Почему бы не воспользоваться параметром forKey в addAnimation? Это было предназначено для этой цели. Просто уберите вызов setValue и переместите строку ключа в вызов addAnimation. Например:

[[hearingAidHalo layer] addAnimation:animation forKey:@"Throb"];

Затем в вашем обратном вызове animationDidStop вы можете сделать что-то вроде:

if (theAnimation == [[hearingAidHalo layer] animationForKey:@"Throb"]) ...
46 голосов
/ 05 января 2012

Я только что нашел еще лучший способ сделать код завершения для 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: finish:, который проверяет наличие блока по указанному ключу и выполняет его, если он найден:

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag
{
  animationCompletionBlock theBlock = [theAnimation valueForKey: kAnimationCompletionBlock];
  if (theBlock)
    theBlock();
}

Прелесть этого подхода в том, что вы можете написать код очистки в том же месте, где вы создаете объект анимации. Более того, поскольку код является блоком, он имеет доступ к локальным переменным в той области видимости, в которой он определен. Вам не нужно возиться с настройкой словарей userInfo или другой подобной ерундой, и вам не нужно писать постоянно растущий метод animationDidStop: finish:, который становится все более сложным по мере добавления различных видов анимации.

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

33 голосов
/ 07 июля 2010

Второй подход будет работать, только если вы явно установите анимацию на , а не будет удалена по завершении перед ее запуском:

CAAnimation *anim = ...
anim.removedOnCompletion = NO;

Если вы этого не сделаете, ваша анимация будет удалена раньше, когда она завершится, и обратный вызов не найдет ее в словаре.

30 голосов
/ 21 сентября 2013

Все остальные ответы слишком сложны! Почему бы вам просто не добавить свой собственный ключ для идентификации анимации?

Это решение очень простое, все что вам нужно - это добавить свой собственный ключ к анимации (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
    }
}

Не требует никаких переменных экземпляра :

13 голосов
/ 16 февраля 2011

Чтобы прояснить, что подразумевается сверху (и что привело меня сюда через несколько потраченных часов): не ожидайте, что исходный объект анимации, который вы выделили, передан вам обратно

 - (void)animationDidStop:(CAAnimation*)animation finished:(BOOL)flag 

когда анимация заканчивается, потому что [CALayer addAnimation:forKey:] делает копию вашей анимации.

На что вы можете положиться, это то, что ключевые значения, которые вы дали своему объекту анимации, все еще там с эквивалентным значением (но не обязательно эквивалентностью указателя) в объекте анимации реплики, передаваемом с сообщением animationDidStop:finished:. Как упомянуто выше, используйте KVC, и вы получите достаточно места для хранения и восстановления состояния.

1 голос
/ 17 января 2017

В большинстве случаев я вижу ответы на вопросы, которые я задаю для 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
        }
    }
}
0 голосов
/ 04 октября 2017

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()
        }
    }
0 голосов
/ 02 апреля 2015

Мне нравится использовать 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];
    }
}
0 голосов
/ 31 августа 2012

Для меня, чтобы проверить, являются ли 2 объекта CABasicAnimation одинаковой анимацией, Я использую функцию keyPath, чтобы сделать именно так.

if ([animationA keyPath] == [animationB keyPath])

  • Нет необходимости устанавливать KeyPath для CABasicAnimation, так как он больше не будет анимироваться
0 голосов
/ 10 августа 2009

ИМХО использование ключа-значения Apple - это элегантный способ сделать это: он специально предназначен для добавления специфических данных приложения к объектам.

Другая, гораздо менее изящная возможность - хранить ссылки на ваши объекты анимации и выполнять сравнение указателей для их идентификации.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...