Làm thế nào để xác định CAAnimation trong đại biểu animationDidStop?


102

Tôi đã gặp sự cố trong đó tôi có một loạt chuỗi CATransition / CAAnimation chồng chéo, tất cả những thứ này tôi cần thực hiện các thao tác tùy chỉnh khi hoạt ảnh dừng lại, nhưng tôi chỉ muốn một trình xử lý ủy quyền cho animationDidStop.

Tuy nhiên, tôi gặp sự cố, dường như không có cách nào để xác định duy nhất mỗi CATransition / CAAnimation trong đại biểu animationDidStop.

Tôi đã giải quyết vấn đề này thông qua hệ thống khóa / giá trị được hiển thị như một phần của CAAnimation.

Khi bạn bắt đầu hoạt ảnh của mình, hãy sử dụng phương thức setValue trên CATransition / CAAnimation để đặt số nhận dạng và giá trị của bạn để sử dụng khi animationDidStop kích hoạt:

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

Trong đại biểu animationDidStop của bạn:

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

Khía cạnh khác của điều này là nó cho phép bạn giữ trạng thái trong hệ thống ghép nối giá trị khóa thay vì phải lưu trữ nó trong lớp đại biểu của bạn. Càng ít mã, càng tốt.

Hãy nhớ xem Tài liệu tham khảo của Apple về Mã hóa cặp giá trị chính .

Có kỹ thuật nào tốt hơn để nhận dạng CAAnimation / CATransition trong đại biểu animationDidStop không?

Cảm ơn, --Batgar


4
Batgar, Khi tôi tìm kiếm "iphone animationDidStophentic", lượt truy cập đầu tiên là bài đăng của bạn, đề xuất việc sử dụng key-value để xác định hoạt ảnh. Chỉ là những gì tôi cần, cảm ơn bạn. Rudi
rudifa 20-08-09

1
Hãy lưu ý rằng CAAnimationdelegaterất mạnh, vì vậy bạn có thể cần đặt nó thành nilđể tránh các chu kỳ lưu giữ!
Iulian Onofrei

Câu trả lời:


92

Kỹ thuật của Batgar quá phức tạp. Tại sao không tận dụng tham số forKey trong addAnimation? Nó được thiết kế cho mục đích này. Chỉ cần thực hiện lệnh gọi setValue và di chuyển chuỗi khóa sang lệnh gọi addAnimation. Ví dụ:

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

Sau đó, trong lệnh gọi lại animationDidStop của bạn, bạn có thể làm điều gì đó như:

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

Tôi muốn đề cập đến điều đó bằng cách sử dụng TĂNG SỐ ĐẾM TĂNG LẠI ở trên! Được cảnh báo. Đó là, animationForKey: tăng số lượng giữ lại của đối tượng CAAnimation của bạn.
mmilo

1
@mmilo Điều đó không phải là quá cao, phải không? Bằng cách thêm hoạt ảnh vào một lớp, lớp đó sẽ sở hữu hoạt ảnh, do đó, số lượng giữ lại của hoạt ảnh tất nhiên sẽ tăng lên.
GorillaPatch

16
Không hoạt động - vào thời điểm bộ chọn dừng được gọi, hoạt ảnh không còn tồn tại. Bạn nhận được một giới thiệu rỗng.
Adam

4
Đó là cách sử dụng sai tham số forKey: và không cần thiết. Những gì Batgar đang làm là hoàn toàn đúng - mã hóa khóa-giá trị cho phép bạn đính kèm bất kỳ dữ liệu tùy ý nào vào hoạt ảnh của mình, vì vậy bạn có thể dễ dàng xác định nó.
matt

7
Adam, hãy xem câu trả lời của jimt bên dưới - bạn phải thiết lập anim.removedOnCompletion = NO;để nó vẫn tồn tại khi -animationDidStop:finished:được gọi.
Yang Meyer,

46

Tôi vừa nghĩ ra một cách thậm chí còn tốt hơn để tạo mã hoàn thành cho CAAnimations:

Tôi đã tạo typedef cho một khối:

typedef void (^animationCompletionBlock)(void);

Và một khóa mà tôi sử dụng để thêm khối vào hoạt ảnh:

#define kAnimationCompletionBlock @"animationCompletionBlock"

Sau đó, nếu tôi muốn chạy mã hoàn thành hoạt ảnh sau khi CAAnimation kết thúc, tôi tự đặt mình làm đại biểu của hoạt ảnh và thêm một khối mã vào hoạt ảnh bằng setValue: forKey:

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

Sau đó, tôi triển khai một phương thức animationDidStop: finish: kiểm tra một khối tại khóa được chỉ định và thực thi nó nếu tìm thấy:

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

Cái hay của cách tiếp cận này là bạn có thể viết mã dọn dẹp ở cùng nơi bạn tạo đối tượng hoạt hình. Vẫn tốt hơn, vì mã là một khối, nó có quyền truy cập vào các biến cục bộ trong phạm vi bao quanh mà nó được xác định. Bạn không phải gặp rắc rối với việc thiết lập từ điển userInfo hoặc những thứ vô nghĩa khác và không cần phải viết hoạt ảnh ngày càng phát triển.

Sự thật mà nói, CAAnimation nên có một thuộc tính khối hoàn thành được tích hợp trong nó và hỗ trợ hệ thống để gọi nó tự động nếu một thuộc tính được chỉ định. Tuy nhiên, đoạn mã trên cung cấp cho bạn chức năng tương tự chỉ với một vài dòng mã bổ sung.


7
Có người cũng đặt cùng một chủng loại trên CAAnimation cho việc này: github.com/xissburg/CAAnimationBlocks
Jay Peyer

Điều này có vẻ không đúng. Khá thường xuyên, tôi nhận được EXEC_Err ngay sau khi theBlock();được gọi, và tôi tin rằng đó là do phạm vi của khối đã bị phá hủy.
mahboudz

Tôi đã sử dụng khối này được một thời gian và nó hoạt động tốt hơn rất nhiều so với cách tiếp cận "chính thức" khủng khiếp của Apple.
Adam

3
Tôi khá chắc chắn rằng bạn cần một [chặn bản sao] khối đó trước khi đặt nó làm giá trị cho một thuộc tính.
Fiona Hopkins

1
Không, bạn không cần sao chép khối.
Duncan C

33

Cách tiếp cận thứ hai sẽ chỉ hoạt động nếu bạn đặt rõ ràng hoạt ảnh của mình để không bị xóa khi hoàn thành trước khi chạy nó:

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

Nếu bạn không làm như vậy, hoạt ảnh của bạn sẽ bị xóa trước khi hoàn thành và lệnh gọi lại sẽ không tìm thấy nó trong từ điển.


10
Đây nên là một bình luận, không phải là một câu trả lời.
Cho đến

2
Tôi tự hỏi liệu có cần thiết phải xóa nó một cách rõ ràng sau đó với removeAnimationForKey không?
bompf

Nó thực sự phụ thuộc vào những gì bạn muốn làm. Bạn có thể xóa nó nếu cần hoặc bỏ nó đi vì bạn muốn làm việc khác song song.
applejack42

31

Tất cả các câu trả lời khác đều quá phức tạp! Tại sao bạn không thêm khóa của riêng mình để xác định hoạt ảnh?

Giải pháp này rất dễ dàng, tất cả những gì bạn cần là thêm khóa của riêng bạn vào hoạt ảnh (trong ví dụ này là animationID)

Chèn dòng này để xác định hoạt ảnh1 :

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

và điều này để xác định animation2 :

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

Kiểm tra nó như thế này:

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

Nó không yêu cầu bất kỳ biến phiên bản nào :


Tôi nhận được một số giá trị int (int (0)) trong animationDidStop như[animation valueForKey:@"animationID"]
abhimuralidharan

14

Để làm rõ những gì được ngụ ý từ phía trên (và điều gì đã đưa tôi đến đây sau một vài giờ lãng phí): đừng mong đợi để xem đối tượng hoạt ảnh ban đầu mà bạn đã cấp phát chuyển lại cho bạn

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

khi hoạt ảnh kết thúc, vì [CALayer addAnimation:forKey:]tạo một bản sao hoạt ảnh của bạn.

Những gì bạn có thể dựa vào, là các giá trị được khóa mà bạn đã cung cấp cho đối tượng hoạt ảnh của mình vẫn ở đó với giá trị tương đương (nhưng không nhất thiết phải là tương đương con trỏ) trong đối tượng hoạt ảnh sao được truyền với animationDidStop:finished:thông báo. Như đã đề cập ở trên, sử dụng KVC và bạn sẽ có đủ phạm vi để lưu trữ và truy xuất trạng thái.


1
+1 Đây là giải pháp tốt nhất! Bạn có thể đặt 'tên' của hoạt ảnh bằng [animation setValue:@"myanim" forKey:@"name"]và thậm chí bạn có thể đặt lớp đang được hoạt ảnh bằng cách sử dụng [animation setValue:layer forKey:@"layer"]. Các giá trị này sau đó có thể được truy xuất trong các phương thức ủy nhiệm.
trojanfoe

valueForKey:trả lại nilcho tôi, bất kỳ ý tưởng tại sao?
Iulian Onofrei

@IulianOnofrei kiểm tra xem hoạt ảnh của bạn không bị thay thế bởi một hoạt ảnh khác cho cùng một thuộc tính - có thể xảy ra tác dụng phụ không mong muốn.
t0

@ t0rst, Xin lỗi, có nhiều hoạt ảnh và sử dụng dán sao chép, tôi đang đặt các giá trị khác nhau trên cùng một biến hoạt ảnh.
Iulian Onofrei

2

Tôi có thể thấy hầu hết các câu trả lời objc, tôi sẽ tạo một câu trả lời cho swift 2.3 dựa trên câu trả lời tốt nhất ở trên.

Để bắt đầu, sẽ rất tốt nếu bạn lưu trữ tất cả các khóa đó trên một cấu trúc riêng tư để nó được nhập an toàn và việc thay đổi nó trong tương lai sẽ không mang lại cho bạn những lỗi khó chịu chỉ vì bạn quên thay đổi nó ở mọi nơi trong mã:

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

Như bạn có thể thấy, tôi đã thay đổi tên của các biến / hoạt ảnh để nó rõ ràng hơn. Bây giờ thiết lập các phím này khi hoạt ảnh được tạo.

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

(...)

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

Sau đó, cuối cùng xử lý đại biểu khi hoạt ảnh dừng

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 sử dụng khóa-giá trị của Apple là một cách làm hay: nó đặc biệt nhằm cho phép thêm dữ liệu ứng dụng cụ thể vào các đối tượng.

Khả năng khác kém thanh lịch hơn nhiều là lưu trữ các tham chiếu đến các đối tượng hoạt ảnh của bạn và thực hiện so sánh con trỏ để xác định chúng.


Điều này sẽ không bao giờ hiệu quả - bạn không thể làm tương đương con trỏ, vì Apple thay đổi con trỏ.
Adam

0

Đối với tôi để kiểm tra xem 2 đối tượng CABasicAnimation có cùng hoạt ảnh hay không, tôi sử dụng hàm keyPath để thực hiện chính xác điều đó.

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

  • Không cần đặt KeyPath cho CABasicAnimation vì nó sẽ không còn hoạt ảnh nữa

các câu hỏi liên quan đến ủy callbacks, và keyPath không phải là một phương pháp trên CAAnimation
Max MacLeod

0

Tôi thích sử dụng setValue:forKey: để giữ tham chiếu về chế độ xem mà tôi đang tạo hoạt ảnh, sẽ an toàn hơn việc cố gắng xác định duy nhất hoạt ảnh dựa trên ID vì cùng một loại hoạt ảnh có thể được thêm vào các lớp khác nhau.

Hai cái này tương đương nhau:

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

với cái này:

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;

và trong phương thức ủy nhiệm:

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

Bạn có thể sử dụng Giá trị chính để liên kết một hoạt ảnh mà bạn đã thêm vào hoạt ảnh được trả về trong phương thức đại biểu animationDidStop.

Khai báo một từ điển để chứa tất cả các hoạt ảnh đang hoạt động và các phần hoàn chỉnh có liên quan:

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

Khi bạn thêm hoạt ảnh của mình, hãy đặt khóa cho nó:

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

Trong animationDidStop, điều kỳ diệu sẽ xảy ra:

    let animObject = anim as NSObject
    if let keyValue = animObject.value(forKey: "CompletionId") as? Int {
        if let completion = animating.removeValue(forKey: keyValue) {
            completion()
        }
    }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.