Everything in this tutorial simply uses a standard UIImageView, no third party libraries are required. When I came across this topic for the first time it was to animate bookmarking a page. It is incredibly easy to create a GIF animation that repeats over and over again. Where the complexity comes in is making the animation run once forward, then being able to toggle it backwards to animate the undo of the action. By adding this simple technique to your objective-c arsenal you can add that little extra to your apps.

In this tutorial we will just create a single view app that uses a button to toggle an animation forward, then touching the button again will run the animation backwards.

This tutorial doesn’t require a whole lot of iOS experience. When I wrote this I used Xcode 4.6.1 targeting iOS 6.1, but the methods used here have been available since iOS 2.0. As a side note, I write in the style of Modern Objective-C, which means things like not synthesizing properties or forward declaring methods.

Getting Started

Start by creating a new Xcode project with the Single View Application template. Use storyboards and Arc. Of course storyboards aren’t required for this, but if you want to follow exactly then select it.

We won’t be using an actual .gif image here. We will take a series of images and cycle through them as frames of an animation. If you already have a set of images you can use those, otherwise you can download this project from GitHub and use the images I created for this tutorial. At this point, add the images to your app’s resources.

Setting up the view

We can just use the ViewController that was created for us by Xcode since we aren’t doing anything more than animating the image in this tutorial.

In the storyboard, add a UIImageView somewhere on our ViewController and fit it to the size of your image (100x100 if you’re using my images). This might be a good time to mention, I’m not a graphic artist, in case my work made you think so. Also drag out a UIButton to use to toggle the animation.

Your storyboard should look similar to the following when you’re done.

Write the code

This whole tutorial is based on the UIImageView class having methods built in to animate an image. All we have to do is give it an array of the images we want to cycle through, a duration, and the number of times to repeat. Then we can tell it to begin animating.

In ViewController.m add an outlet property for your image view.

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIImageView *starImageView;
@end

Add the following code to your viewDidLoad method:

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSMutableArray *starImageArray = [@[] mutableCopy];
    for (int i = 0; i < 8; i++) {
        UIImage *starImage = [UIImage imageNamed:[NSString stringWithFormat:@"star_%d", i]];
        [starImageArray addObject:starImage];
    }

    self.starImageView.animationImages = starImageArray;
    self.starImageView.animationRepeatCount = 1;
    self.starImageView.animationDuration = 0.267;

    self.starImageView.image = starImageArray[0];
}

The first thing we did here is instantiate a mutable array, so we can add our images one by one. When I created my images, I gave them all the same name with an incrementing number at the end. So, I iterate through a for loop once for each of my images. I grab the image and add it to the mutable array.

Next, we pass the array of images to the animationImages property of our image view, starImageView. With an animationRepeatCount of 1, the image animation will run one time. You might think you want it to repeat 0 times, but a value of 0 will make it loop forever.

The documentation states that the default value of the animationDuration is the number of images multiplied by 1/30th, but in my experience if I don’t explicitly set this myself it jumps through too fast. You can set this to anything you want, but for a smooth animation, the (frames/30) works well, so in my case with 8 images I used 0.267.

The documentation also states that if the animationImages property is set, the image set in starImageView.image will be hidden. Once again I have not found this to be the case, so we explicitly set the image property to the first image in the animation array. It will, however, hide the image while the animation is actually taking place.

Now we just need to create an IBAction method for our button to trigger the animation.

- (IBAction)animateButtonTouched
{
    [self.starImageView startAnimating];
}

In the method we just make a call to -startAnimating on our image view.

Now jump back to the storyboard and connect your UIImageView to the starImageView outlet, and connect your UIButton’s touchUpInside action to our animateButtonTouched method.

Build and run here. Click the button and see what happens with our animation.

…This isn’t exactly what we’re looking for. The animation runs through once just how we want it, but instead of staying on the last image, it goes back to the first frame.

We need to get a handle on when the image finishes its animation so we can change its image property to the last image in our sequence. Unfortunately there is no completion block or method for animationDidComplete.

Don’t worry! We can fix this.

Replace your animateButtonTouched method with the following:

- (IBAction)animateButtonTouched
{
    if (!self.starImageView.isAnimating) {
        [self.starImageView startAnimating];
        [self.starImageView setImage:[self.starImageView.animationImages lastObject]];
        [self performSelector:@selector(starAnimationDidFinish) withObject:nil afterDelay:self.starImageView.animationDuration];
    }
}

First of all, we want to prevent the spamming of our animation button which could wreak all kinds of havoc. So, before anything, we check if it is already animating. If not, then startAnimating.

Next, we set the image property to the last image in our series, so when the animation ends and our image property is no longer hidden it will be set to the final image.

This next line is where we can get a handle on when the animation ends. We tell it to perform selector (which we will write in a minute) after a delay of the animation duration.

Now add the following method to your ViewController.m:

- (void)starAnimationDidFinish
{
    NSArray *reversedImages = [[self.starImageView.animationImages reverseObjectEnumerator] allObjects];
    self.starImageView.animationImages = reversedImages;
}

When the animation finishes, we are going to prep it for the reverse animation. NSArray objects have a useful method reverseObjectEnumerator that allows you to traverse an array in reverse order. If you call allObjects on this, then you get the flipped array. We take the flipped array and assign it to the animationImages property.

Conclusion

That’s all it takes. If you build and run now, each time you touch the button it should toggle your animation forward and back. You can get the completed project from my GitHub here. Or, here is a complete code listing of ViewController.m, so you can see it all together.

#import "ViewController.h"

@interface ViewController ()
@property (nonatomic, weak) IBOutlet UIImageView *starImageView;
@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSMutableArray *starImageArray = [@[] mutableCopy];
    for (int i = 0; i < 8; i++) {
        UIImage *starImage = [UIImage imageNamed:[NSString stringWithFormat:@"star_%d", i]];
        [starImageArray addObject:starImage];
    }

    self.starImageView.animationImages = starImageArray;
    self.starImageView.animationRepeatCount = 1;
    self.starImageView.animationDuration = 0.267;

    self.starImageView.image = starImageArray[0];
}

- (IBAction)animateButtonTouched
{
    if (!self.starImageView.isAnimating) {
        [self.starImageView startAnimating];
        [self.starImageView setImage:[self.starImageView.animationImages lastObject]];
        [self performSelector:@selector(starAnimationDidFinish) withObject:nil afterDelay:self.starImageView.animationDuration];
    }
}

- (void)starAnimationDidFinish
{
    NSArray *reversedImages = [[self.starImageView.animationImages reverseObjectEnumerator] allObjects];
    self.starImageView.animationImages = reversedImages;
}

@end