Animated images on the iPhone – sans memory leaks!

For those of you who have developed on the iPhone, you may have cringed at the prospect of having an animated image in your app, due to the flaky operation of the UIImageView which is the de-facto method of making a simple image animation.

For small images and animations, UIImageView works well. However, when you get to bigger and longer animations, it simply doesn’t scale. You might be fooled by the simulator, but on the real device your images will load very slowly before succumbing to the limited memory available.

Fortunately, there are other methods in which to playback and animated image. The first and perhaps the best method would be to implement everything in GLES. The second, which will be described here might be easier if you don’t want to setup GLES, considering it uses standard UIKit.

To start with

The Challenge

Here’s the theory: UIImageView sucks, so we need another method in which to animate images.

__Method__ __Problem__
Use UIImageView It doesn’t scale
Re-draw a UIView every frame Far too slow
Use GLES Beyond the scope of this article
Transform a clipped UIView each frame None, perfect!

As you can see, there are only two realistic methods in which to animate our image: either use GLES, or transform a view around the screen.

Since I don’t want to be messing around with GLES right now, i’ll tackle the UIView, or flip-book approach.

Illustration of Concept

A flip-book can be made either by putting an image in a UIScrollView and scrolling through it, or by making a custom clipped UIView with the image inserted as a subview.

(The latter approach which I prefer as it is a bit cleaner)

The code put simply

To start off with, your master view needs to be set to clip its subviews on init. i.e.:

self.clipsToBounds = YES;

And the subview, which should be a UIView which basically needs to draw a big image containing all the frames, needs to have user interaction disabled. i.e.:

// MYAnimationViewSlide code

- (void)setImage:(UIImage*)aValue {
  [image release];
  image = [aValue retain];

  // Resize to fit
  CGSize sz = image.size;
  self.frame = CGRectMake(0, 0, sz.width, sz.height);

  [self setNeedsDisplay];
}

- (void)drawRect:(CGRect)rect {
  // ...
  // Just draw the image!
  [image drawAtPoint:CGPointMake(0, 0)];

  // No more need for the image
  [image release];
  image = nil;
  // ...
}

// in init...
  self.userInteractionEnabled = NO;

Then all you need to do is move your slide around in your view by setting the .transform property, i.e.:

// MYAnimationView code

// slide size == child view size
CGSize slideSz = slide.bounds.size;
// frame size == root view size
CGSize viewSz = self.bounds.size;

// Calculate x,y position for the frame
// (assuming your frames are packed in a block rather than a strip)
int framesPerRow = floor(slideSz.width / viewSz.width);
int x = currentFrame % framesPerRow;
int y = currentFrame / framesPerRow;

slide.transform = CGAffineTransformMakeTranslation(-viewSz.width*x, 
                                                   -viewSz.height*y);

While tied to an NSTimer:

// MYAnimationView code

advanceTime = (1.0 / 25.0);
animTimer = [NSTimer scheduledTimerWithTimeInterval:advanceTime
                     target:self 
                     selector:@selector(animateTimer:) 
                     userInfo:nil repeats:YES];

// ...

- (void)animateTimer:(NSTimer*)timer
{
  NSTimeInterval timeInterval=timer.timeInterval;
  NSTimeInterval currentTime=[NSDate timeIntervalSinceReferenceDate];

  if (lastTime != 0) {
    double delta = (currentTime - lastTime) / timeInterval;

    // advance by at least 1 frame, at most frames in delta
    if (delta < 1)
      self.currentFrame += 1;
    else
      self.currentFrame += (int)delta;
  }

  lastTime = currentTime;
}

It’s as simple as that!

(unless your slides exceed 1024×1024, in which case you need to use more than one slide. i’ll let you figure that one out)

The Code

For those of you who are interested in the code for this flip-book, I have pasted a gist on github. As always, feel free to fork!

And in case you want an example image, here’s a set of frames for a 50 frame, 96×66 animation.

anim