จะระบุ 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.
 }

อีกแง่มุมหนึ่งคือช่วยให้คุณสามารถรักษาสถานะในระบบการจับคู่ค่าคีย์แทนที่จะต้องเก็บไว้ในคลาสตัวแทนของคุณ รหัสยิ่งน้อยยิ่งดี

ให้แน่ใจว่าได้ตรวจสอบแอปเปิ้ลในการอ้างอิงที่สำคัญราคาคู่ Coding

มีเทคนิคที่ดีกว่าสำหรับการระบุ CAAnimation / CATransition ในตัวแทน AnimationDidStop หรือไม่

ขอบคุณ - แบทการ์


4
Batgar เมื่อฉันใช้ googled สำหรับ "iphone animationDidStop ระบุ" Hit แรกคือโพสต์ของคุณซึ่งแนะนำให้ใช้คีย์ - ค่าเพื่อระบุภาพเคลื่อนไหว สิ่งที่ฉันต้องการขอบคุณ Rudi
rudifa

1
โปรดทราบว่าCAAnimation's delegateมีความแข็งแกร่งดังนั้นคุณอาจจะต้องตั้งมันไปnilเพื่อหลีกเลี่ยงการรักษารอบ!
Iulian Onofrei

คำตอบ:


92

เทคนิคของ Batgar ซับซ้อนเกินไป ทำไมไม่ใช้ประโยชน์จากพารามิเตอร์ forKey ใน addAnimation มีไว้เพื่อการนี้มาก เพียงแค่โทรออกไปที่ setValue และย้ายสตริงคีย์ไปที่การเรียก addAnimation ตัวอย่างเช่น:

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

จากนั้นในการเรียกกลับ animationDidStop ของคุณคุณสามารถทำสิ่งต่างๆเช่น:

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

ฉันอยากจะพูดถึงว่าการใช้ข้างต้นเป็นจำนวนเงินที่เหลือ! ขอเตือน. นั่นคือ animationForKey: เพิ่มจำนวนการคงไว้ของวัตถุ CAAnimation ของคุณ
mmilo

1
@mmilo นั่นไม่หวือหวามากใช่มั้ย? การเพิ่มภาพเคลื่อนไหวลงในเลเยอร์เลเยอร์จะเป็นเจ้าของภาพเคลื่อนไหวดังนั้นจำนวนการคงไว้ของภาพเคลื่อนไหวจึงเพิ่มขึ้นแน่นอน
GorillaPatch

17
ไม่ทำงานเมื่อมีการเรียกตัวเลือกการหยุดภาพเคลื่อนไหวจะไม่มีอยู่อีกต่อไป คุณได้รับการอ้างอิงที่เป็นโมฆะ
อดัม

4
นั่นเป็นการใช้พารามิเตอร์ forKey: ในทางที่ผิดและไม่จำเป็นต้องใช้ สิ่งที่ Batgar กำลังทำนั้นถูกต้อง - การเข้ารหัสคีย์ - ค่าช่วยให้คุณสามารถแนบข้อมูลใด ๆ ก็ได้กับภาพเคลื่อนไหวของคุณเพื่อให้คุณสามารถระบุได้อย่างง่ายดาย
แมตต์

7
อดัมดูคำตอบของ jimt ด้านล่าง - คุณต้องตั้งค่าanim.removedOnCompletion = NO;เพื่อให้ยังคงมีอยู่เมื่อ-animationDidStop:finished:ถูกเรียก
Yang Meyer

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: finished: ซึ่งตรวจสอบบล็อกที่คีย์ที่ระบุและดำเนินการหากพบ:

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

ความสวยงามของวิธีนี้คือคุณสามารถเขียนโค้ดล้างข้อมูลในที่เดียวกับที่คุณสร้างออบเจ็กต์แอนิเมชั่น ยังดีกว่าเนื่องจากโค้ดเป็นบล็อกจึงมีการเข้าถึงตัวแปรภายในในขอบเขตการปิดล้อมที่กำหนดไว้ คุณไม่ต้องวุ่นวายกับการตั้งค่าพจนานุกรม userInfo หรือเรื่องไร้สาระอื่น ๆ และไม่จำเป็นต้องเขียนแอนิเมชั่น DidStop: finished: method ที่เพิ่มมากขึ้นเรื่อย ๆ เมื่อคุณเพิ่มแอนิเมชั่นประเภทต่างๆ

จะบอกความจริง CAAnimation ควรมีคุณสมบัติบล็อกที่สมบูรณ์ในตัวและระบบรองรับการเรียกใช้โดยอัตโนมัติหากมีการระบุไว้ อย่างไรก็ตามโค้ดด้านบนจะให้ฟังก์ชันเดียวกันกับคุณโดยมีโค้ดพิเศษเพียงไม่กี่บรรทัด


7
นอกจากนี้ยังมีคนจัดหมวดหมู่ไว้ใน CAAnimation สำหรับเรื่องนี้: github.com/xissburg/CAAnimationBlocks
Jay Peyer

สิ่งนี้ดูเหมือนจะไม่ถูกต้อง บ่อยครั้งที่ฉันได้รับ EXEC_Err ทันทีที่theBlock();ถูกเรียกใช้และฉันเชื่อว่าเป็นเพราะขอบเขตของบล็อกถูกทำลาย
mahboudz

ฉันใช้บล็อกมาระยะหนึ่งแล้วและมันก็ใช้ได้ผลดีกว่าวิธี "อย่างเป็นทางการ" ของ Apple
Adam

3
ฉันค่อนข้างแน่ใจว่าคุณต้อง [บล็อกสำเนา] บล็อกนั้นก่อนที่จะตั้งเป็นค่าสำหรับคุณสมบัติ
Fiona Hopkins

1
ไม่คุณไม่จำเป็นต้องคัดลอกบล็อก
Duncan C

33

แนวทางที่สองจะใช้ได้ผลก็ต่อเมื่อคุณตั้งค่าอย่างชัดเจนว่าจะไม่ลบภาพเคลื่อนไหวของคุณเมื่อเสร็จสิ้นก่อนที่จะรัน:

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

หากคุณไม่ทำเช่นนั้นภาพเคลื่อนไหวของคุณจะถูกลบออกก่อนเมื่อเสร็จสมบูรณ์และการโทรกลับจะไม่พบในพจนานุกรม


10
นี่ควรเป็นความคิดเห็นไม่ใช่คำตอบ
จนถึง

2
ฉันสงสัยว่าจำเป็นต้องลบออกอย่างชัดเจนในภายหลังด้วย removeAnimationForKey หรือไม่?
bompf

มันขึ้นอยู่กับสิ่งที่คุณต้องการทำจริงๆ คุณสามารถลบออกได้ถ้าจำเป็นหรือปล่อยไว้เพราะต้องการทำอย่างอื่นควบคู่
applejack42

31

คำตอบอื่น ๆ ทั้งหมดซับซ้อนเกินไป! ทำไมคุณไม่เพิ่มคีย์ของคุณเองเพื่อระบุภาพเคลื่อนไหว

โซลูชันนี้ง่ายมากเพียงแค่เพิ่มคีย์ของคุณเองลงในแอนิเมชั่น (รหัสภาพเคลื่อนไหวในตัวอย่างนี้)

แทรกบรรทัดนี้เพื่อระบุภาพเคลื่อนไหว 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"]จากนั้นสามารถเรียกค่าเหล่านี้ได้ภายในวิธีการมอบหมาย
โทรจัน

valueForKey:ผลตอบแทนnilสำหรับฉันคิดว่าทำไม?
Iulian Onofrei

@IulianOnofrei ตรวจสอบว่าภาพเคลื่อนไหวของคุณไม่ได้ถูกแทนที่ด้วยภาพเคลื่อนไหวอื่นสำหรับคุณสมบัติเดียวกัน - อาจเกิดขึ้นได้เนื่องจากผลข้างเคียงที่ไม่คาดคิด
t0rst

@ t0rst ขออภัยมีภาพเคลื่อนไหวหลายภาพและใช้การคัดลอกวางฉันตั้งค่าต่างกันในตัวแปรภาพเคลื่อนไหวเดียวกัน
Iulian Onofrei

2

ฉันเห็นคำตอบ 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
        }
    }
}

0

IMHO โดยใช้คีย์ - ค่าของ Apple เป็นวิธีที่ยอดเยี่ยมในการดำเนินการนี้โดยเฉพาะเพื่อให้สามารถเพิ่มข้อมูลเฉพาะของแอปพลิเคชันลงในวัตถุได้

ความเป็นไปได้ที่สวยงามน้อยกว่ามากอื่น ๆ คือการจัดเก็บการอ้างอิงไปยังวัตถุแอนิเมชั่นของคุณและทำการเปรียบเทียบตัวชี้เพื่อระบุสิ่งเหล่านั้น


สิ่งนี้จะไม่ได้ผล - คุณไม่สามารถทำการเทียบเท่าตัวชี้ได้เนื่องจาก Apple เปลี่ยนตัวชี้
อดัม

0

สำหรับฉันเพื่อตรวจสอบว่าวัตถุ CABasicAnimation 2 ตัวเป็นภาพเคลื่อนไหวเดียวกันหรือไม่ฉันใช้ฟังก์ชัน keyPath เพื่อทำตามนั้น

ถ้า ([animationA keyPath] == [animationB keyPath])

  • ไม่จำเป็นต้องตั้งค่า KeyPath สำหรับ CABasicAnimation เนื่องจากจะไม่เคลื่อนไหวอีกต่อไป

คำถามเกี่ยวข้องกับการโทรกลับของผู้รับมอบสิทธิ์และ keyPath ไม่ใช่วิธีการใน CAAnimation
Max MacLeod

0

ฉันชอบใช้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];
    }
}

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()
        }
    }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.