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



спасибо,
-- Батгар

491   10  

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

    Ничего не найдено.