开发者

Animation using array of images in sequence

I have an array of images which I want to animate by playing these images one after the other in a sequence. I want to repeat the whole开发者_运维技巧 loop several times. I am developing a game for iPad. Suggest to me a method to achieve this functionality in Objective-C with the Cocoa framework.


NSArray *animationArray = [NSArray arrayWithObjects:
                          [UIImage imageNamed:@"images.jpg"],
                          [UIImage imageNamed:@"images1.jpg"],
                          [UIImage imageNamed:@"images5.jpg"],
                          [UIImage imageNamed:@"index3.jpg"],
                          nil];
UIImageView *animationView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0,320, 460)];
animationView.backgroundColor      = [UIColor purpleColor];
animationView.animationImages      = animationArray;
animationView.animationDuration    = 1.5;
animationView.animationRepeatCount = 0;
[animationView startAnimating]; 
[self.view addSubview:animationView];       
[animationView release];

add your own images in the array.repeat Count 0 means infinite loop.You can give your own number also.


There are at least 3 ways to animate an array of images through a UIImageView. I'm adding 3 links to download sample code for the 3 possibilities.

The first one is the one that everyone knows. The other ones are less known.

- UIImageView.animationImages
Example Link

The problem of this one is that do not have Delegate to tell us in which moment the animation is finished. So, we can have problems if we want to display something after the animation.

In the same way, there is no possibility to kept the last image from the animation in the UIImageView automatically. If we combine both problems we can have a gap at the end of the animation if we want to kept the last frame on screen.

self.imageView.animationImages = self.imagesArray; // the array with the images
self.imageView.animationDuration = kAnimationDuration; // static const with your value
self.imageView.animationRepeatCount = 1;
[self.imageView startAnimating];

- CAKeyframeAnimation
Example Link

This way to animate works through CAAnimation. It have an easy delegate to use and we can know when the animation finish.

This is probably the best way to animate an array of images.

- (void)animateImages
{
    CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"contents"];
    keyframeAnimation.values = self.imagesArray;

    keyframeAnimation.repeatCount = 1.0f;
    keyframeAnimation.duration = kAnimationDuration; // static const with your value

    keyframeAnimation.delegate = self;

    //    keyframeAnimation.removedOnCompletion = YES;
    keyframeAnimation.removedOnCompletion = NO;
    keyframeAnimation.fillMode = kCAFillModeForwards;

    CALayer *layer = self.animationImageView.layer;

    [layer addAnimation:keyframeAnimation
                 forKey:@"girlAnimation"];
}

Delegate:

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    if (flag)
    {
        // your code
    }
}

- CADisplayLink
Example Link

A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.

This way to do it is really interesting and opens a lot of possibilities to manipulate what are we showing in screen.

DisplayLink getter:

- (CADisplayLink *)displayLink
{
    if (!_displayLink)
    {
        _displayLink = [CADisplayLink displayLinkWithTarget:self
                                                   selector:@selector(linkProgress)];
    }

    return _displayLink;
}

Methods:

- (void)animateImages
{
    self.displayLink.frameInterval = 5;
    self.frameNumber = 0;
    [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
                           forMode:NSRunLoopCommonModes];
}

- (void)linkProgress
{
    if (self.frameNumber > 16)
    {
        [self.displayLink invalidate];
        self.displayLink = nil;
        self.animationImageView.image = [UIImage imageNamed:@"lastImageName"];
        self.imagesArray = nil;
        return;
    }

    self.animationImageView.image = self.imagesArray[self.frameNumber++];
    self.frameNumber++;
}

GENERAL PROBLEM:

Even though we have this 3 possibilities, if your animation is with a lot of big images, consider using a video instead. The usage of memory will decrease a lot.

A General problem you will face doing this is in the moment of the allocation of the images.

If you use [UIImage imageNamed:@"imageName"] you will have cahe problems.

From Apple:

This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method locates and loads the image data from disk or asset catelog, and then returns the resulting object. You can not assume that this method is thread safe.

So, imageNamed: stores the image in a private Cache.
- The first problem is that you can not take control of the cache size.
- The second problem is that the cache did not get cleaned in time and if you are allocating a lot of images with imageNamed:, your app, probably, will crash.

SOLUTION:

Allocate images directly from Bundle:

NSString *imageName = [NSString stringWithFormat:@"imageName.png"];
NSString *path = [[NSBundle mainBundle] pathForResource:imageName

// Allocating images with imageWithContentsOfFile makes images to do not cache.
UIImage *image = [UIImage imageWithContentsOfFile:path];

Small problem:

Images in Images.xcassets get never allocated. So, move your images outside Images.xcassets to allocate directly from Bundle.


See the animationImages property of UIImageView. It’s hard to say if it fits your needs as you don’t give us details, but it’s a good start.


I have added a swift 3.0 extension for this

extension UIImageView {

    func animate(images: [UIImage], index: Int = 0, completionHandler: (() -> Void)?) {

        UIView.transition(with: self, duration: 0.5, options: .transitionCrossDissolve, animations: {
            self.image = images[index]

        }, completion: { value in
            let idx = index == images.count-1 ? 0 : index+1

            if idx == 0 {
                completionHandler!()

            } else {
                self.animate(images: images, index: idx, completionHandler: completionHandler)
            }

        })
    }

}


Best solution for me use CADisplayLink. UIImageView doesn't have completion block and you can't catch steps of animation. In my task i must changing background of view with image sequencer step by step. So CADisplayLink allows you handling steps and finishing animation. If we talk about usage of memory, i think best solution load images from bundle and delete array after finishing

ImageSequencer.h

typedef void (^Block)(void);

@protocol ImageSequencerDelegate;

@interface QSImageSequencer : UIImageView
@property (nonatomic, weak) id <ImageSequencerDelegate> delegate;

- (void)startAnimatingWithCompletionBlock:(Block)block;
@end

@protocol ImageSequencerDelegate <NSObject>
@optional
- (void)animationDidStart;
- (void)animationDidStop;
- (void)didChangeImage:(UIImage *)image;
@end

ImageSequencer.m

- (instancetype)init {
    if (self = [super init]) {

        _imagesArray = [NSMutableArray array];

        self.image = [self.imagesArray firstObject];
    }

    return self;
}


#pragma mark - Animation

- (void)startAnimating {
    [self startAnimatingWithCompletionBlock:nil];
}


- (void)startAnimatingWithCompletionBlock:(Block)block {
    self.frameNumber = 0;

    [self setSuccessBlock:block];

    self.displayLink.frameInterval = 5;

    if (self.delegate && [self.delegate respondsToSelector:@selector(animationDidStart)]) {
        [self.delegate animationDidStart];
    }

    [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
                           forMode:NSRunLoopCommonModes];
}


-(void)stopAnimating {
     self.image = [self.imagesArray lastObject];

    [self.displayLink invalidate];
    [self setDisplayLink:nil];
    Block block_ = [self successBlock];
    if (block_) {
        block_();
    }

    if (self.delegate && [self.delegate respondsToSelector:@selector(animationDidStop)]) {
        [self.delegate animationDidStop];
    }

    [self.imagesArray removeAllObjects];
}


- (void)animationProgress {
    if (self.frameNumber >= self.imagesArray.count) {
        [self stopAnimating];
        return;
    }

    if (self.delegate && [self.delegate respondsToSelector:@selector(didChangeImage:)]) {
        [self.delegate didChangeImage:self.imagesArray[self.frameNumber]];
    }

    self.image = self.imagesArray[self.frameNumber];
    self.frameNumber++;
}


#pragma mark - Getters / Setters

- (CADisplayLink *)displayLink {
    if (!_displayLink){
        _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(animationProgress)];
    }

    return _displayLink;
}

- (NSMutableArray<UIImage *> *)imagesArray {
    if (_imagesArray.count == 0) {
        // get images from bundle and set to array
    }
    return _imagesArray;
}

@end


This is a simple and working code for animation./

-(void)move
{
    [UIView animateWithDuration:5
                          delay:0.0
                        options: UIViewAnimationCurveEaseOut
                     animations:^{
                         [_imgbox setFrame:CGRectMake(100, 100, _imgbox.frame.size.width, _imgbox.frame.size.height)];
                     }
                     completion:^(BOOL finished){
                         NSLog(@"Done!");
                     }];
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜