Bagaimana cara mengidentifikasi CAAnimation dalam delegasi animationDidStop?

102

Saya memiliki masalah di mana saya memiliki serangkaian urutan CATransition / CAAnimation yang tumpang tindih, yang semuanya saya perlukan untuk melakukan operasi kustom saat animasi berhenti, tetapi saya hanya menginginkan satu penangan delegasi untuk animationDidStop.

Namun, saya punya masalah, sepertinya tidak ada cara untuk mengidentifikasi secara unik setiap CATransition / CAAnimation dalam delegasi animationDidStop.

Saya memecahkan masalah ini melalui sistem kunci / nilai yang diekspos sebagai bagian dari CAAnimation.

Saat Anda memulai animasi, gunakan metode setValue pada CATransition / CAAnimation untuk menyetel pengenal dan nilai Anda yang akan digunakan saat animationDidStop diaktifkan:

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

Dalam delegasi animationDidStop Anda:

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

Aspek lain dari ini adalah memungkinkan Anda untuk mempertahankan status dalam sistem penyandingan nilai kunci alih-alih harus menyimpannya di kelas delegasi Anda. Semakin sedikit kode, semakin baik.

Pastikan untuk memeriksa Referensi Apple tentang Pengodean Pasangan Nilai Kunci .

Apakah ada teknik yang lebih baik untuk identifikasi CAAnimation / CATransition di delegasi animationDidStop?

Terima kasih, --Batgar

Batgar
sumber
4
Batgar, Ketika saya mencari di Google untuk "iphone animationDidStop identifikasi", klik pertama adalah posting Anda, menyarankan penggunaan nilai kunci untuk mengidentifikasi animasinya. Hanya yang saya butuhkan, terima kasih. Rudi
rudifa
1
Sadarilah bahwa CAAnimationitu delegatekuat, jadi Anda mungkin perlu mengaturnya niluntuk menghindari siklus penahanan!
Iulian Onofrei

Jawaban:

92

Teknik Batgar terlalu rumit. Mengapa tidak memanfaatkan parameter forKey di addAnimation? Itu dimaksudkan untuk tujuan ini. Cukup lakukan panggilan ke setValue dan pindahkan string kunci ke panggilan addAnimation. Sebagai contoh:

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

Kemudian, dalam callback animationDidStop, Anda dapat melakukan sesuatu seperti:

if (theAnimation == [[hearingAidHalo layer] animationForKey:@"Throb"]) ...
vocaro
sumber
Saya ingin menyebutkan bahwa menggunakan INCREMENTS THE RETAIN COUNT di atas! Berhati-hatilah. Artinya, animationForKey: menambah jumlah retensi objek CAAnimation Anda.
mmilo
1
@mmilo Itu tidak terlalu mengejutkan, bukan? Dengan menambahkan animasi ke lapisan, lapisan tersebut memiliki animasi, sehingga jumlah penahan animasi tentu saja bertambah.
GorillaPatch
16
Tidak berfungsi - pada saat pemilih berhenti dipanggil, animasi sudah tidak ada lagi. Anda mendapatkan referensi nol.
Adam
4
Itu adalah penyalahgunaan parameter forKey:, dan tidak diperlukan. Apa yang dilakukan Batgar benar-benar benar - pengkodean nilai kunci memungkinkan Anda melampirkan data sembarang ke animasi Anda, sehingga Anda dapat dengan mudah mengidentifikasinya.
matt
7
Adam, lihat jawaban jimt di bawah ini - Anda harus mengaturnya anim.removedOnCompletion = NO;agar tetap ada saat -animationDidStop:finished:dipanggil.
Yang Meyer
46

Saya baru saja menemukan cara yang lebih baik untuk melakukan kode penyelesaian untuk CAAnimations:

Saya membuat typedef untuk sebuah blok:

typedef void (^animationCompletionBlock)(void);

Dan kunci yang saya gunakan untuk menambahkan blok ke animasi:

#define kAnimationCompletionBlock @"animationCompletionBlock"

Kemudian, jika saya ingin menjalankan kode penyelesaian animasi setelah CAAnimation selesai, saya menetapkan diri saya sebagai delegasi animasi, dan menambahkan blok kode ke animasi menggunakan setValue: forKey:

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

Kemudian, saya mengimplementasikan metode animationDidStop: finish:, yang memeriksa blok pada kunci yang ditentukan dan menjalankannya jika ditemukan:

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

Keunggulan dari pendekatan ini adalah Anda dapat menulis kode pembersihan di tempat yang sama tempat Anda membuat objek animasi. Lebih baik lagi, karena kodenya adalah sebuah blok, ia memiliki akses ke variabel lokal dalam lingkup terlampir yang ditentukannya. Anda tidak perlu repot menyiapkan kamus userInfo atau omong kosong lainnya, dan tidak perlu menulis metode animationDidStop: finish: yang terus berkembang yang menjadi semakin kompleks saat Anda menambahkan berbagai jenis animasi.

Sejujurnya, CAAnimation harus memiliki properti blok penyelesaian yang dibangun di dalamnya, dan dukungan sistem untuk memanggilnya secara otomatis jika ada yang ditentukan. Namun, kode di atas memberi Anda fungsionalitas yang sama dengan hanya beberapa baris kode tambahan.

Duncan C
sumber
7
Seseorang juga menyusun kategori di CAAnimation untuk ini: github.com/xissburg/CAAnimationBlocks
Jay Peyer
Ini sepertinya tidak benar. Cukup sering, saya mendapatkan EXEC_Err tepat setelah theBlock();dipanggil, dan saya yakin itu disebabkan oleh fakta bahwa cakupan blok dihancurkan.
mahboudz
Saya telah menggunakan blok ini untuk beberapa waktu, dan ini bekerja JAUH lebih baik daripada pendekatan "resmi" Apple yang mengerikan.
Adam
3
Saya cukup yakin bahwa Anda memerlukan untuk [memblokir salinan] blok itu sebelum menyetelnya sebagai nilai untuk properti.
Fiona Hopkins
1
Tidak, Anda tidak perlu menyalin blok tersebut.
Duncan C
33

Pendekatan kedua hanya akan berfungsi jika Anda secara eksplisit menyetel animasi Anda agar tidak dihapus saat selesai sebelum menjalankannya:

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

Jika Anda gagal melakukannya, animasi Anda akan dihapus sebelumnya setelah selesai, dan callback tidak akan menemukannya di kamus.

jimt
sumber
10
Ini harus menjadi komentar, bukan jawaban.
Hingga
2
Saya ingin tahu apakah perlu untuk menghapusnya secara eksplisit setelahnya dengan removeAnimationForKey?
bompf
Itu sangat tergantung pada apa yang ingin Anda lakukan. Anda dapat melepasnya jika perlu atau membiarkannya karena Anda ingin melakukan hal lain secara bersamaan.
applejack42
31

Semua jawaban lain terlalu rumit! Mengapa Anda tidak menambahkan kunci Anda sendiri untuk mengidentifikasi animasi?

Solusi ini sangat mudah, yang Anda butuhkan hanyalah menambahkan kunci Anda sendiri ke animasi (animationID dalam contoh ini)

Sisipkan baris ini untuk mengidentifikasi animasi1 :

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

dan ini untuk mengidentifikasi animasi2 :

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

Ujilah seperti ini:

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

Itu tidak memerlukan variabel contoh apa pun :

Tibidabo
sumber
Saya mendapatkan beberapa nilai int (int (0)) di animationDidStop sebagai[animation valueForKey:@"animationID"]
abhimuralidharan
14

Untuk membuat eksplisit apa yang tersirat dari atas (dan apa yang membawa saya ke sini setelah beberapa jam yang terbuang): jangan berharap untuk melihat objek animasi asli yang Anda alokasikan kembali kepada Anda oleh

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

saat animasi selesai, karena [CALayer addAnimation:forKey:]membuat salinan animasi Anda.

Apa yang dapat Anda andalkan, adalah bahwa nilai kunci yang Anda berikan ke objek animasi Anda masih ada dengan nilai yang setara (tetapi tidak harus setara dengan penunjuk) di objek animasi replika yang diteruskan dengan animationDidStop:finished:pesan. Seperti disebutkan di atas, gunakan KVC dan Anda mendapatkan ruang lingkup yang luas untuk menyimpan dan mengambil status.

t0rst
sumber
1
+1 Ini adalah solusi terbaik! Anda dapat mengatur 'nama' animasi dengan [animation setValue:@"myanim" forKey:@"name"]dan Anda bahkan dapat mengatur lapisan yang dianimasikan menggunakan [animation setValue:layer forKey:@"layer"]. Nilai ini kemudian dapat diambil dalam metode delegasi.
trojanfoe
valueForKey:kembali niluntukku, tahu kenapa?
Iulian Onofrei
@IulianOnofrei memeriksa bahwa animasi Anda tidak digantikan oleh animasi lain untuk properti yang sama - dapat terjadi sebagai efek samping yang tidak terduga.
t0rst
@ t0rst, Maaf, memiliki beberapa animasi dan menggunakan copy paste, saya menyetel nilai yang berbeda pada variabel animasi yang sama.
Iulian Onofrei
2

Saya dapat melihat sebagian besar jawaban objc. Saya akan membuat satu untuk 2.3 cepat berdasarkan jawaban terbaik di atas.

Sebagai permulaan, akan sangat baik untuk menyimpan semua kunci tersebut di sebuah private struct sehingga ini adalah tipe yang aman dan mengubahnya di masa depan tidak akan membawa Anda bug yang mengganggu hanya karena Anda lupa mengubahnya di mana-mana dalam kode:

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

Seperti yang Anda lihat, saya telah mengubah nama variabel / animasi agar lebih jelas. Sekarang atur tombol-tombol ini saat animasi dibuat.

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

(...)

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

Lalu akhirnya menangani delegasi saat animasi berhenti

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
        }
    }
}
apinho
sumber
0

IMHO menggunakan nilai kunci Apple adalah cara elegan untuk melakukan ini: ini secara khusus dimaksudkan untuk memungkinkan penambahan data spesifik aplikasi ke objek.

Kemungkinan lain yang kurang elegan adalah menyimpan referensi ke objek animasi Anda dan melakukan perbandingan penunjuk untuk mengidentifikasinya.

Teemu Kurppa
sumber
Ini tidak akan berhasil - Anda tidak dapat melakukan kesetaraan penunjuk, karena Apple mengubah penunjuk.
Adam
0

Bagi saya untuk memeriksa apakah 2 objek CABasicAnimation adalah animasi yang sama, saya menggunakan fungsi keyPath untuk melakukan persis seperti itu.

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

  • Tidak perlu menyetel KeyPath untuk CABasicAnimation karena tidak akan dianimasikan lagi
Sirisilp Kongsilp
sumber
pertanyaannya terkait dengan mendelegasikan callback, dan keyPath bukanlah metode di CAAnimation
Max MacLeod
0

Saya suka menggunakan setValue:forKey: untuk menyimpan referensi tampilan yang saya animasikan, ini lebih aman daripada mencoba mengidentifikasi animasi secara unik berdasarkan ID karena jenis animasi yang sama dapat ditambahkan ke lapisan yang berbeda.

Keduanya setara:

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

dengan yang ini:

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;

dan dalam metode delegasi:

- (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];
    }
}
Andrei Marincas
sumber
0

Xcode 9 Swift 4.0.0 Memperbarui

Anda dapat menggunakan Nilai Kunci untuk menghubungkan animasi yang Anda tambahkan ke animasi yang dikembalikan dalam metode delegasi animationDidStop.

Deklarasikan kamus berisi semua animasi aktif dan penyelesaian terkait:

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

Saat Anda menambahkan animasi Anda, setel kunci untuk itu:

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

Dalam animationDidStop, keajaiban terjadi:

    let animObject = anim as NSObject
    if let keyValue = animObject.value(forKey: "CompletionId") as? Int {
        if let completion = animating.removeValue(forKey: keyValue) {
            completion()
        }
    }
Eng Yew
sumber