Access Method After UIImageView Animation Finish
Asked Answered
H

8

12

I have an array of images loaded into a UIImageView that I am animating through one cycle. After the images have been displayed, I would like a @selector to be called in order to dismiss the current view controller. The images are animated fine with this code:

NSArray * imageArray = [[NSArray alloc] initWithObjects:
                        [UIImage imageNamed:@"HowTo1.png"], 
                        [UIImage imageNamed:@"HowTo2.png"],
                        nil];
UIImageView * instructions = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

instructions.animationImages = imageArray;
[imageArray release];
instructions.animationDuration = 16.0;
instructions.animationRepeatCount = 1;
instructions.contentMode = UIViewContentModeBottomLeft;
[instructions startAnimating];
[self.view addSubview:instructions];
[instructions release];

After the 16 seconds, I would like a method to be called. I looked into the UIView class method setAnimationDidStopSelector: but I cannot get it to work with my current animation implementation. Any suggestions?

Thanks.

Hardiman answered 14/2, 2012 at 19:50 Comment(0)
F
25

Use performSelector:withObject:afterDelay::

[self performSelector:@selector(animationDidFinish:) withObject:nil
    afterDelay:instructions.animationDuration];

or use dispatch_after:

dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, instructions.animationDuration * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    [self animationDidFinish];
});
Fulfillment answered 14/2, 2012 at 19:56 Comment(5)
This method seems to work fine, thanks rob! But i was wondering what would happen if for some reason the iPhone's frame-rate drop's a lot. Lets say from 30 to 15. In that case the animation wouldn't be finished playing when the dispatch timer run's out right?Shiekh
Why do you think the frame rate dropping would make the animation take longer? Maybe the image view drops frames if it can't keep up.Fulfillment
This is exactly the issue I am having with performSelector and delay. The array is too slow to load and loses time. Precision is lost. I need some other solution...Twitty
dispatch_after is inaccurate and can't be used in animation. Use CADisplayLink insteadCabernet
CADisplayLink is good for syncing to individual display updates. This question is about detecting the end of an animation whose duration is not tied to the display's frame rate. However, if I were to try answering this question again today, I'd experiment with a wrapping the UIImageView animation in a CATransaction with a completion block.Fulfillment
T
24

You can simple use this category UIImageView-AnimationCompletionBlock

And for Swift version: UIImageView-AnimationImagesCompletionBlock

Take a look on ReadMe file in this class..

Add UIImageView+AnimationCompletion.h and UIImageView+AnimationCompletion.m files in project and them import UIImageView+AnimationCompletion.h file where you want to use this..

For eg.

NSMutableArray *imagesArray = [[NSMutableArray alloc] init];
for(int i = 10001 ; i<= 10010 ; i++)
    [imagesArray addObject:[UIImage imageNamed:[NSString stringWithFormat:@"%d.png",i]]];

self.animatedImageView.animationImages = imagesArray;
self.animatedImageView.animationDuration = 2.0f;
self.animatedImageView.animationRepeatCount = 3;
[self.animatedImageView startAnimatingWithCompletionBlock:^(BOOL success){
 NSLog(@"Animation Completed",);}];
Telesthesia answered 25/1, 2014 at 3:28 Comment(1)
I can't figure out how to use this in swift. After adding to bridging header it still doesn't work.Undermost
E
8

There is no way to do this precisely with UIImageView. Using a delay will work most of the time, but there can be some lag after startAnimating is called before the animation happens on screen so its not precise. Rather than using UIImageView for this animation try using a CAKeyframeAnimation which will call a delegate method when the animation is finished. Something along these lines:

NSArray * imageArray = [[NSArray alloc] initWithObjects:
                    (id)[UIImage imageNamed:@"HowTo1.png"].CGImage, 
                    (id)[UIImage imageNamed:@"HowTo2.png"].CGImage,
                    nil];
UIImageView * instructions = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

//create animation
CAKeyframeAnimation *anim = [CAKeyframeAnimation animation];
[anim setKeyPath:@"contents"];
[anim setValues:imageArray];

[anim setRepeatCount:1];
[anim setDuration:1];
anim.delegate = self; // delegate animationDidStop method will be called

CALayer *myLayer = instructions.layer;

[myLayer addAnimation:anim forKey:nil];

Then just implement the animationDidStop delegate method

Eastward answered 2/5, 2013 at 17:20 Comment(3)
Can't add CGImage to array.Totem
To get rid of the warning caused by adding CGImage to an array just cast it to id.Eastward
Answer edited to reflect above. See apples doc.Eastward
S
4

With this method you avoid timers to fire while the imageView is still animating due to slow image loading. You could use both performSelector: withObject: afterDelay: and GCD in this way:

[self performSelector:@selector(didFinishAnimatingImageView:)
           withObject:imageView
           afterDelay:imageView.animationDuration];

/!\ self.imageView.animationDuration has to bet configured before startAnimating, otherwise it will be 0

Than if -(void)didFinishAnimatingImageView:create a background queue, perform a check on the isAnimating property, than execute the rest on the main queue

- (void)didFinishAnimatingImageView:(UIImageView*)imageView
{

   dispatch_queue_t backgroundQueue = dispatch_queue_create("com.yourcompany.yourapp.checkDidFinishAnimatingImageView", 0);
   dispatch_async(backgroundQueue, ^{

       while (self.imageView.isAnimating)
           NSLog(@"Is animating... Waiting...");

       dispatch_async(dispatch_get_main_queue(), ^{

          /* All the rest... */

       });

   });

}
Surge answered 14/10, 2013 at 19:44 Comment(0)
G
4

Created Swift Version from GurPreet_Singh Answer (UIImageView+AnimationCompletion) I wasn't able to create a extension for UIImageView but I believe this is simple enough to use.

class AnimatedImageView: UIImageView, CAAnimationDelegate {

    var completion: ((_ completed: Bool) -> Void)?

    func startAnimate(completion: ((_ completed: Bool) -> Void)?) {
        self.completion = completion
        if let animationImages = animationImages {
            let cgImages = animationImages.map({ $0.cgImage as AnyObject })
            let animation = CAKeyframeAnimation(keyPath: "contents")
            animation.values = cgImages
            animation.repeatCount = Float(self.animationRepeatCount)
            animation.duration = self.animationDuration
            animation.delegate = self

            self.layer.add(animation, forKey: nil)
        } else {
            self.completion?(false)
        }
    }

    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        completion?(flag)
    }
}


let imageView = AnimatedImageView(frame: CGRect(x: 50, y: 50, width: 200, height: 135))
imageView.image = images.first
imageView.animationImages = images
imageView.animationDuration = 2
imageView.animationRepeatCount = 1
view.addSubview(imageView)

imageView.startAnimate { (completed) in
     print("animation is done \(completed)")
     imageView.image = images.last
}
Gallon answered 29/8, 2017 at 18:6 Comment(1)
Works like a charm only thing I added was animation.calculationMode = .discrete to disable the interpolation between the frames. It makes it look like a low fps gif.Brut
J
3

You can create a timer to fire after the duration of your animation. The callback could process your logic you want to execute after the animation finishes. In your callback be sure check the status of the animation [UIImageView isAnimating] just in case you want more time to let the animation finish.

Jaworski answered 14/2, 2012 at 19:55 Comment(0)
C
1

in ImageView.m

- (void) startAnimatingWithCompleted:(void (^)(void))completedHandler {
if (!self.isAnimating) {
    [self startAnimating];
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, self.animationDuration * NSEC_PER_SEC);
    dispatch_after(popTime, dispatch_get_main_queue(),completedHandler);       
}}
Chancre answered 8/4, 2013 at 8:17 Comment(0)
F
-1

Swift

func setupImageView() {
    
    if let GIF = UIImage().getGIF(imageName: "giphy") {
        
        let gifImageView = UIImageView(frame: .init(x: 0, y: 0, width: 100, height: 100))
        gifImageView.animationImages = GIF.images
        gifImageView.animationDuration = GIF.duration
        gifImageView.animationRepeatCount = 1
        gifImageView.startAnimating()
        
        DispatchQueue.main.asyncAfter(deadline: .now() + GIF.duration, execute: {
            
            print("GIF Ended")
            gifImageView.stopAnimating()
        })
    }
}

extension UIImage {
    
    func getGIF(imageName: String) -> UIImage? {
        
        let url = Bundle.main.url(forResource: imageName, withExtension: "gif")
        
        if let url = url {
            
            do {
                
                let imageData = try Data(contentsOf: url)
                
                return UIImage.animatedImage(withAnimatedGIFData: imageData)
                
            } catch {
                
                print("Unable to load data: \(error)")
                
                return nil
            }
        }
        
        return nil
    }
}
Feathering answered 29/4, 2023 at 6:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.