Intro to iOS Animation

Simple iOS animations are straightforward to implement. For example, say you’ve got an image on your storyboard that you want to to fly into position when the view controller first loads. To make this happen, first move the image off-screen before the view is displayed. Then, when the view appears, move the image back to its original location inside of a call to animateWithDuration:

- (void)viewDidLayoutSubviews {
  // Move image off the screen to the left
  self.myImage.center = CGPointMake(self.myImage.center.x - 300,
                                    self.myImage.center.y);
}

- (void)viewDidAppear:(BOOL)animated {
  [UIView animateWithDuration:1 animations:^{
    // Move image back to where it started
    self.myImage.center = CGPointMake(self.myImage.center.x + 300,
                                      self.myImage.center.y);
  }];
}

Click to view animation.
Enter left. (Click to view.)

Keep it Clean with a Category

Let’s say you want to do something more complicated where images fly in from all sides. The code already starts to get messy:

- (void)viewDidLayoutSubviews {
  self.orangeBall.center = CGPointMake(self.orangeBall.center.x + 300,
                                       self.orangeBall.center.y);
  self.greenBall.center  = CGPointMake(self.greenBall.center.x,
                                       self.greenBall.center.y + 300);
  self.yellowBall.center = CGPointMake(self.yellowBall.center.x,
                                       self.yellowBall.center.y - 300);
  self.blueBall.center   = CGPointMake(self.blueBall.center.x - 300,
                                       self.blueBall.center.y);
}

- (void)viewDidAppear:(BOOL)animated {
  [UIView animateWithDuration:1 animations:^{
    self.orangeBall.center = CGPointMake(self.orangeBall.center.x - 300,
                                         self.orangeBall.center.y);
    self.greenBall.center  = CGPointMake(self.greenBall.center.x,
                                         self.greenBall.center.y - 300);
    self.yellowBall.center = CGPointMake(self.yellowBall.center.x,
                                         self.yellowBall.center.y + 300);
    self.blueBall.center   = CGPointMake(self.blueBall.center.x + 300,
                                         self.blueBall.center.y);
  }];
}

Four balls flying in.
Multi-fly-in. (Click to view.)

The code above gets the job done, but it’s difficult to understand and would be difficult to change. It would nice if we had an easier way to move our image views around, and we can accomplish this with a category. In Objective-C, a category allows you to add functionality to an existing class, like NSArray, similar to monkey patching. But unlike monkey patching, only the files that explicitly import a category will obtain any of that category’s functionality.

Let’s add a category to UIView. In a file called UIView+AnimationHelpers.m, we can add the following (we also need a corresponding .h file):

#import "UIView+AnimationHelpers.h"

@implementation UIView (AnimationHelpers)

- (void) moveLeft: (CGFloat) offset {
  self.center = CGPointMake(self.center.x - offset, self.center.y);
}

- (void) moveRight: (CGFloat) offset {
  self.center = CGPointMake(self.center.x + offset, self.center.y);
}

- (void) moveUp: (CGFloat) offset {
  self.center = CGPointMake(self.center.x, self.center.y - offset);
}

- (void) moveDown: (CGFloat) offset {
  self.center = CGPointMake(self.center.x, self.center.y + offset);
}

@end

After we import our new category, our animation code becomes simpler:

- (void)viewDidLayoutSubviews {
  [self.orangeBall moveRight:300];
  [self.greenBall moveDown:300];
  [self.yellowBall moveUp:300];
  [self.blueBall moveLeft:300];
}

- (void)viewDidAppear:(BOOL)animated {
  [UIView animateWithDuration:2 animations:^{
    [self.orangeBall moveLeft:300];
    [self.greenBall moveUp:300];
    [self.yellowBall moveDown:300];
    [self.blueBall moveRight:300];
  }];
}

For Categories with State, Associate

We can further improve our animation code by adding a moveBack method that moves a UIView back to its previous location. To do that, we’d like to add a private instance variable that refers to the object’s last location. But Objective-C categories don’t allow that. Instead, we need to use an associative reference.

Each object in Objective-C contains a dictionary that stores key/value pairs. Associative references give you access to that data. The result is a simple idea (similar to instance variables), but associative references require tricky syntax. (For example, the dictionary key type is void*.) Here’s how we can implement moveBack:

#import "UIView+AnimationHelpers.h"
#import 

@implementation UIView (AnimationHelpers)

static void * const kLastCenterKey = (void*)&kLastCenterKey;

- (void) moveBack {
  CGPoint tempCenter = self.center;
  self.center = [self lastCenter];
  self.lastCenter = tempCenter;
}

- (void)setLastCenter:(CGPoint) point {
  NSValue* value = [NSValue valueWithCGPoint:point];
  objc_setAssociatedObject(self, kLastCenterKey, value, OBJC_ASSOCIATION_COPY);
}

- (CGPoint)lastCenter {
  NSValue* value = objc_getAssociatedObject(self, kLastCenterKey);
  return [value CGPointValue];
}

- (void) moveLeft: (CGFloat) offset {
  self.lastCenter = self.center;
  self.center = CGPointMake(self.center.x - offset, self.center.y);
}

- (void) moveRight: (CGFloat) offset {
  self.lastCenter = self.center;
  self.center = CGPointMake(self.center.x + offset, self.center.y);
}

- (void) moveUp: (CGFloat) offset {
  self.lastCenter = self.center;
  self.center = CGPointMake(self.center.x, self.center.y - offset);
}

- (void) moveDown: (CGFloat) offset {
  self.lastCenter = self.center;
  self.center = CGPointMake(self.center.x, self.center.y + offset);
}

@end

The tricky parts are:

  • The definition of the key. (I mean, look at it.)
  • Calling objc_getAssociatedObject and objc_setAssociatedObject correctly.

Despite the weird syntax, though, you’re just working with key/value pairs in the end. And once you’ve implemented your category, you can reap the reward of cleaner view controller code. Here’s the final (entire) version:

#import "AnimationViewController.h"
#import "UIView+AnimationHelpers.h"

@interface AnimationViewController ()
@property (weak, nonatomic) IBOutlet UIImageView *orangeBall;
@property (weak, nonatomic) IBOutlet UIImageView *greenBall;
@property (weak, nonatomic) IBOutlet UIImageView *yellowBall;
@property (weak, nonatomic) IBOutlet UIImageView *blueBall;
@end

@implementation AnimationViewController

- (void)viewDidLayoutSubviews {
  [self.orangeBall moveRight:300];
  [self.greenBall moveDown:300];
  [self.yellowBall moveUp:300];
  [self.blueBall moveLeft:300];
}

- (void)viewDidAppear:(BOOL)animated {
  [UIView animateWithDuration:1 animations:^{
    [self.orangeBall moveBack];
    [self.greenBall moveBack];
    [self.yellowBall moveBack];
    [self.blueBall moveBack];
  }];
}

@end