AnimationDidStop temsilcisindeki CAAnimation nasıl belirlenir?


102

Animasyonlar durduğunda tümü özel işlemler gerçekleştirmem gereken bir dizi örtüşen CATransition / CAAnimation dizisine sahip olduğumda bir sorun yaşadım, ancak animationDidStop için yalnızca bir delege işleyici istedim.

Ancak, bir sorun yaşadım, animationDidStop temsilcisindeki her bir CATransition / CAAnimation'ı benzersiz bir şekilde tanımlamanın bir yolu yok gibi görünüyordu.

CAAnimation'ın bir parçası olarak ortaya çıkan anahtar / değer sistemi aracılığıyla bu sorunu çözdüm.

Animasyonunuzu başlattığınızda, animationDidStop tetiklendiğinde kullanılacak tanımlayıcılarınızı ve değerlerinizi ayarlamak için CATransition / CAAnimation'da setValue yöntemini kullanın:

-(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 temsilcinizde:

- (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.
 }

Bunun diğer yönü, durumu temsilci sınıfınızda saklamak yerine anahtar değeri eşleştirme sisteminde tutmanıza izin vermesidir. Daha az kod, daha iyi.

Anahtar Değer Çifti Kodlaması ile ilgili Apple Referansını kontrol ettiğinizden emin olun .

AnimationDidStop delegesinde CAAnimation / CATransition tanımlama için daha iyi teknikler var mı?

Teşekkürler --Batgar


4
Batgar, Google'da "iphone animasyonuDidStop tanımla" için arama yaptığımda, ilk isabet sizin gönderinizdi ve animasyonu tanımlamak için anahtar / değer kullanımı önerdi. Tam ihtiyacım olan şey, teşekkür ederim. Rudi
rudifa

1
Unutmayın CAAnimation'ler delegatesen ayarlayın gerekebilir, böylece güçlü nildöngüleri korumak önlemek için!
Iulian Onofrei

Yanıtlar:


92

Batgar'ın tekniği çok karmaşık. Neden addAnimation'daki forKey parametresinden yararlanmıyorsunuz? Tam da bu amaç için tasarlanmıştı. Sadece setValue çağrısını alın ve anahtar dizesini addAnimation çağrısına taşıyın. Örneğin:

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

Ardından, animationDidStop geri aramanızda aşağıdaki gibi bir şey yapabilirsiniz:

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

Yukarıdaki ARTIŞLARI KALAN SAYISI'nı kullanarak belirtmek isterim! Dikkatli olun. Yani, animationForKey: CAAnimation nesnenizin tutma sayısını artırır.
mmilo

1
@mmilo Bu çok şaşırtıcı değil, değil mi? Bir katmana bir animasyon ekleyerek, katman animasyonun sahibi olur, bu nedenle animasyonun tutma sayısı elbette artar.
GorillaPatch

17
Çalışmıyor - durdurma seçici çağrıldığında, animasyon artık mevcut değildir. Boş referans alırsınız.
Adam

4
Bu, forKey: parametresinin kötüye kullanılmasıdır ve buna gerek yoktur. Batgar'ın yaptığı şey tam olarak doğru - anahtar-değer kodlaması, animasyonunuza herhangi bir rastgele veriyi eklemenize izin verir, böylece onu kolayca tanımlayabilirsiniz.
mat

7
Adam, jimt'in aşağıdaki cevabına bakın - çağrıldığında anim.removedOnCompletion = NO;hala var olacak şekilde ayarlamalısınız -animationDidStop:finished:.
Yang Meyer

46

CAAnimations için tamamlama kodu yapmanın daha da iyi bir yolunu buldum:

Bir blok için bir typedef oluşturdum:

typedef void (^animationCompletionBlock)(void);

Ve bir animasyona blok eklemek için kullandığım bir anahtar:

#define kAnimationCompletionBlock @"animationCompletionBlock"

Ardından, bir CAAnimation bittikten sonra animasyon tamamlama kodunu çalıştırmak istersem, kendimi animasyonun temsilcisi olarak belirlerim ve setValue: forKey kullanarak animasyona bir kod bloğu eklerim:

animationCompletionBlock theBlock = ^void(void)
{
  //Code to execute after the animation completes goes here    
};
[theAnimation setValue: theBlock forKey: kAnimationCompletionBlock];

Ardından, belirtilen anahtarda bir bloğu kontrol eden ve bulunursa onu çalıştıran bir animationDidStop: finish: yöntemini uyguluyorum:

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

Bu yaklaşımın güzelliği, temizleme kodunu, animasyon nesnesini oluşturduğunuz yere yazabilmenizdir. Daha da iyisi, kod bir blok olduğundan, tanımlandığı çevreleyen kapsamdaki yerel değişkenlere erişimi vardır. UserInfo sözlüklerini veya diğer bu tür saçmalıkları ayarlamakla uğraşmak zorunda değilsiniz ve sürekli büyüyen bir animationDidStop: finish: farklı türde animasyonlar ekledikçe daha da karmaşık hale gelen bir yöntem yazmak zorunda değilsiniz.

Gerçeği söylemek gerekirse, CAAnimation'da yerleşik bir tamamlama bloğu özelliği ve belirtilirse otomatik olarak çağırmak için sistem desteği olmalıdır. Bununla birlikte, yukarıdaki kod size aynı işlevi yalnızca birkaç satırlık fazladan kodla verir.


7
Birisi bunun için CAAnimation'da bir kategori oluşturdu: github.com/xissburg/CAAnimationBlocks
Jay Peyer

Bu doğru görünmüyor. Çoğunlukla, çalıştırıldıktan hemen sonra bir EXEC_Err alıyorum theBlock();ve bunun bloğun kapsamının yok edilmiş olmasından kaynaklandığına inanıyorum.
mahboudz

Bloğu bir süredir kullanıyorum ve Apple'ın korkunç "resmi" yaklaşımından ÇOK daha iyi çalışıyor.
Adam

3
Bir mülk için bir değer olarak ayarlamadan önce bu engellemeyi [kopyayı engelle] almanız gerektiğinden oldukça eminim.
Fiona Hopkins

1
Hayır, bloğu kopyalamanıza gerek yok.
Duncan C

33

İkinci yaklaşım sadece iş açıkça için animasyon ayarlar eğer değil çalıştırmadan önce tamamlanması üzerine çıkarılmalıdır:

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

Bunu yapmazsanız, animasyonunuz tamamlanmadan kaldırılır ve geri arama onu sözlükte bulamaz.


10
Bu bir cevap değil, bir yorum olmalıdır.
Till

2
Daha sonra removeAnimationForKey ile açıkça kaldırılması gerekip gerekmediğini merak ediyorum.
bompf

Gerçekten ne yapmak istediğine bağlı. Gerekirse çıkarabilir veya art arda başka bir şey yapmak istediğiniz için bırakabilirsiniz.
applejack42

31

Diğer tüm cevaplar çok karmaşık! Neden animasyonu tanımlamak için kendi anahtarınızı eklemiyorsunuz?

Bu çözüm çok kolaydır, tek ihtiyacınız olan animasyona kendi anahtarınızı eklemektir (bu örnekte animationID)

Animasyonu tanımlamak için bu satırı ekleyin1 :

[myAnimation1 setValue:@"animation1" forKey:@"animationID"];

ve bu animasyon2'yi tanımlamak için :

[myAnimation2 setValue:@"animation2" forKey:@"animationID"];

Bunu şu şekilde test edin:

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

Herhangi bir örnek değişkeni gerektirmez :


Animasyonda bazı int değeri (int (0)) [animation valueForKey:@"animationID"]
alıyorumDidStop

14

Yukarıdan neyin ima edildiğini (ve boşa geçen birkaç saatin ardından beni buraya getiren şeyin) açık bir şekilde ifade etmek için: tahsis ettiğiniz orijinal animasyon nesnesinin size geri dönmesini beklemeyin

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

animasyon bittiğinde, çünkü [CALayer addAnimation:forKey:] bir kopyasını oluşturur.

Güvenebileceğiniz şey, animasyon nesnenize verdiğiniz anahtarlı değerlerin, animationDidStop:finished:mesajla iletilen replika animasyon nesnesindeki eşdeğer değerde (ancak zorunlu olarak işaretçi eşdeğerliği değil) hala orada olmasıdır . Yukarıda belirtildiği gibi, KVC'yi kullanın ve durumu depolamak ve almak için geniş kapsam elde edersiniz.


1
+1 Bu en iyi çözüm! İle animasyonun 'adını' [animation setValue:@"myanim" forKey:@"name"]ayarlayabilir ve hatta kullanarak canlandırılan katmanı bile ayarlayabilirsiniz [animation setValue:layer forKey:@"layer"]. Bu değerler daha sonra temsilci yöntemleri içinde alınabilir.
trojanfoe

valueForKey:nilbenim için döner , neden olduğu hakkında bir fikriniz var mı?
Iulian Onofrei

@IulianOnofrei animasyonunuzun aynı özellik için başka bir animasyon tarafından değiştirilmediğini kontrol edin - beklenmedik bir yan etki olarak ortaya çıkabilir.
t0rst

@ t0rst, Üzgünüm, birden fazla animasyon var ve kopyala yapıştır kullanarak aynı animasyon değişkeninde farklı değerler ayarlıyordum.
Iulian Onofrei

2

Çoğunlukla objc cevaplarını görebiliyorum, yukarıdaki en iyi cevaba göre hızlı 2.3 için bir tane yapacağım.

Başlangıç ​​olarak, tüm bu anahtarları özel bir yapıda saklamak iyi olacaktır, böylece bu tür güvenlidir ve gelecekte onu değiştirmek, kodun her yerinde değiştirmeyi unuttuğunuz için size sinir bozucu hatalar getirmeyecektir:

private struct AnimationKeys {
    static let animationType = "animationType"
    static let volumeControl = "volumeControl"
    static let throbUp = "throbUp"
}

Gördüğünüz gibi, değişkenlerin / animasyonların isimlerini değiştirdim, böylece daha net. Şimdi animasyon oluşturulduğunda bu tuşları ayarlayın.

volumeControlAnimation.setValue(AnimationKeys.volumeControl, forKey: AnimationKeys.animationType)

(...)

throbUpAnimation.setValue(AnimationKeys.throbUp, forKey: AnimationKeys.animationType)

Sonunda, temsilciyi animasyonun durduğu zaman için ele almak

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

Apple'ın anahtar / değer çiftini kullanan IMHO, bunu yapmanın zarif yoludur: özellikle nesnelere uygulamaya özel verilerin eklenmesine izin vermek içindir.

Diğer çok daha az zarif olasılık, animasyon nesnelerinize referansları depolamak ve bunları tanımlamak için bir işaretçi karşılaştırması yapmaktır.


Bu asla işe yaramayacaktır - Apple işaretçiyi değiştirdiği için işaretçi denkleştirme yapamazsınız.
Adam

0

2 CABasicAnimation nesnesinin aynı animasyon olup olmadığını kontrol etmem için, tam olarak bunu yapmak için keyPath işlevini kullanıyorum.

eğer ([animationA keyPath] == [animationB keyPath])

  • Artık canlandırmayacağı için CABasicAnimation için KeyPath'i ayarlamaya gerek yoktur.

soru, temsilci geri aramalarıyla ilgilidir ve keyPath, CAAnimation'da bir yöntem değildir
Max MacLeod

0

Kullanmayı seviyorum setValue:forKey: canlandırdığım görünümün referansını tutmak için, animasyonu kimliğe göre benzersiz bir şekilde tanımlamaya çalışmaktan daha güvenlidir çünkü aynı türden animasyon farklı katmanlara eklenebilir.

Bu ikisi eşdeğerdir:

[UIView animateWithDuration: 0.35
                 animations: ^{
                     myLabel.alpha = 0;
                 } completion: ^(BOOL finished) {
                     [myLabel removeFromSuperview];
                 }];

Bununla birlikte:

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;

ve temsilci yönteminde:

- (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 temsilci yönteminde döndürülen animasyona eklediğiniz bir animasyonu ilişkilendirmek için Anahtar Değerleri kullanabilirsiniz.

Tüm aktif animasyonları ve ilgili tamamlamaları içeren bir sözlük bildirin:

 var animationId: Int = 1
 var animating: [Int : () -> Void] = [:]

Animasyonunuzu eklediğinizde, bunun için bir anahtar belirleyin:

moveAndResizeAnimation.setValue(animationId, forKey: "CompletionId")
animating[animationId] = {
    print("completion of moveAndResize animation")
}
animationId += 1    

AnimationDidStop'da sihir gerçekleşir:

    let animObject = anim as NSObject
    if let keyValue = animObject.value(forKey: "CompletionId") as? Int {
        if let completion = animating.removeValue(forKey: keyValue) {
            completion()
        }
    }
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.