Як визначити CAAnimation у делегата animationDidStop?


102

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

Спасибі, - Батгар


4
Батгар, Коли я переглянув "iphone animationDidStop Identify", першим зверненням став ваш пост, який пропонує використовувати ключ-значення для ідентифікації анімації. Просто те, що мені було потрібно, дякую. Rudi
rudifa

1
Майте в виду , що CAAnimation«s delegateсильна, так що вам , можливо , доведеться встановити його , nilщоб уникнути збереження циклів!
Юліан Онофрей

Відповіді:


92

Техніка Батгара занадто складна. Чому б не скористатися параметром forKey в addAnimation? Це було призначено саме для цієї мети. Просто вийміть виклик до setValue і перемістіть рядок ключа до виклику addAnimation. Наприклад:

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

Потім у зворотному дзвінку animationDidStop можна зробити щось на кшталт:

if (theAnimation == [[hearingAidHalo layer] animationForKey:@"Throb"]) ...

Я хотів би зазначити, що, використовуючи вищевказані ВПРАВКИ, ЗАСТОСУЄТЬСЯ КОНТУР! Будьте попереджені. Тобто animationForKey: збільшує кількість залишків вашого об'єкта CAAnimation.
mmilo

1
@mmilo Це не дуже надмірно, чи не так? Додаючи анімацію до шару, шар володіє анімацією, тому кількість утримуваних анімації, природно, збільшується.
GorillaPatch

16
Не працює - до моменту виклику селектора зупинки анімація більше не існує. Ви отримуєте нульову інформацію.
Адам

4
Це неправильне використання параметра forKey: і в цьому немає необхідності. Те, що робив Батгар, є абсолютно правильним - кодування ключа-значення дозволяє приєднувати до анімації будь-які довільні дані, щоб ви могли легко їх визначити.
мат

7
Адам, дивіться відповідь Джитта нижче - ви повинні встановити anim.removedOnCompletion = NO;так, щоб він все ще існував, коли -animationDidStop:finished:викликається.
Ян Мейєр

46

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

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

Краса такого підходу полягає в тому, що ви можете написати код очищення там, де ви створюєте об’єкт анімації. А ще краще, оскільки код є блоком, він має доступ до локальних змінних в тій області, в якій він визначений. Вам не доведеться возитись із налаштуванням словників userInfo чи інших подібних дурниць, і не потрібно писати постійно зростаючий анімаціюDidStop: готовий: метод, який стає все складнішим у міру додавання різних видів анімації.

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


7
Хтось також склав категорію на CAAnimation для цього: github.com/xissburg/CAAnimationBlocks
Jay

Це здається не правильним. Досить часто я отримую EXEC_Err відразу після theBlock();виклику, і я вважаю, що це пов'язано з тим, що область блоку була знищена.
mahboudz

Я вже деякий час використовую блок, і він працює НАШЕ краще, ніж жахливий "офіційний" підхід Apple.
Адам

3
Я досить впевнений, що вам знадобиться [блокувати копіювати] цей блок, перш ніж встановлювати його як значення для властивості.
Фіона Хопкінс

1
Ні, вам не потрібно копіювати блок.
Дункан C

33

Другий підхід працюватиме лише у тому випадку, якщо ви явно встановили, що анімація не буде видалена після завершення перед її запуском:

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

Якщо цього не зробити, ваша анімація буде видалена раніше, коли вона завершиться, і зворотний виклик не знайде її у словнику.


10
Це має бути коментар, а не відповідь.
До

2
Цікаво, чи потрібно явно видалити його згодом за допомогою RemoveAnimationForKey?
bompf

Це дійсно залежить, чим ти хочеш займатися. Ви можете зняти його за потреби або залишити його, бо хочете зробити щось інше в тандемі.
applejack42

31

Всі інші відповіді занадто складні! Чому б вам просто не додати свій власний ключ, щоб визначити анімацію?

Це рішення дуже просто, все що вам потрібно - це додати свій анімаційний ключ (анімаціяID в цьому прикладі)

Вставте цей рядок, щоб визначити анімацію1 :

[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
    }
}

Він не вимагає змінних примірників :


Я отримую деяке значення int (int (0)) в animationDidStop as[animation valueForKey:@"animationID"]
abhimuralidharan

14

Щоб чітко сказати, що мається на увазі зверху (і що мене привело сюди через кілька витрачених годин): не сподівайтеся, що оригінальний анімаційний об’єкт, який ви виділили, передає вам назад

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

коли анімація закінчується, тому що [CALayer addAnimation:forKey:]робить копію вашої анімації.

На що ви можете покластися, це те, що ключові значення, які ви надали своєму об’єкту анімації, все ще є з еквівалентним значенням (але не обов'язково еквівалентністю вказівника) в об'єкті анімації репліка, переданому разом із animationDidStop:finished:повідомленням. Як було сказано вище, використовуйте KVC, і ви отримуєте достатній обсяг для зберігання та отримання стану.


1
+1 Це найкраще рішення! Ви можете встановити "ім'я" анімації, [animation setValue:@"myanim" forKey:@"name"]і ви навіть можете встановити шар, який анімується за допомогою [animation setValue:layer forKey:@"layer"]. Потім ці значення можна отримати в рамках делегатних методів.
trojanfoe

valueForKey:повертається nilдля мене, будь-яка ідея чому?
Юліан Онофрей

@IulianOnofrei перевірте, чи ваша анімація не зміщена іншою анімацією для того ж властивості - може статися як несподіваний побічний ефект.
t0rst

@ t0rst, Вибачте, маючи кілька анімацій та використовуючи пасту для копіювання, я встановлював різні значення на одну і ту ж змінну анімації.
Юліан Онофрей

2

Я бачу в основному відповіді на objc, я зроблю для швидкої 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

IMHO, що використовує ключове значення Apple, є елегантним способом цього: це спеціально призначене для дозволу додавання конкретних додатків даних до об'єктів.

Інша набагато менш елегантна можливість - це зберігання посилань на ваші анімаційні об’єкти та порівняння вказівників для їх ідентифікації.


Це ніколи не вийде - ви не можете виконати еквівалентність покажчика, тому що Apple змінює вказівник.
Адам

0

Для мене, щоб перевірити, чи 2 об'єкт CABasicAnimation однакова анімація, я використовую функцію keyPath, щоб зробити саме це.

if ([анімаціяA keyPath] == [анімаціяB keyPath])

  • Не потрібно встановлювати KeyPath для CABasicAnimation, оскільки він більше не буде анімувати

питання стосується делегування зворотних викликів, а keyPath не є методом CAAnimation
Макс Маклеод

0

Мені подобається використовувати 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

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()
        }
    }
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.