Upward Mobility: Animating the Leaves of Fall (in Australia)

It doesn't take many lines of code to make a snazzy animated background in iOS

One of nicest features of iOS development is that, frequently, you can pull off visual effects that look amazing without having to write a lot of code. It may be about to start the spring season here, but Down Under it’s heading into fall, so to honor those folks, we’ll animate some falling leaves. We’ll use a simple piece of code that will animate objects drifting down the screen.

I say drifting, because this trick only works for objects that are going to maintain a constant velocity as they descend through the space. Thus, it works well for things like leaves, pieces of paper, or other light objects that the mind will immediately accept as having reached their terminal velocity. In my case, I developed the code to handle confetti streaming down a “You’ve won” screen. Heavier or more streamlined objects require a bit more work to animate, because you need to simulate the effects of gravity and acceleration, otherwise the animation doesn’t look right.

So, to begin, we need some image assets. For this example, I’m going to use 3 leaf images I grabbed from a public clip art site, and cleverly renamed to leaf1.png, leaf2.png, and leaf3.png. I created a simple single-view storyboard project, and added a label in the center of the view. Now, all that’s left is to plop in the code:

#define DegreesToRadians(x) ((x) * M_PI / 180.0)
#define LEAF_DROP_INTERVAL 2
#define NUMBER_OF_LEAF_TYPES 3
#define DRIFT_X_VARIANCE 40
#define ROTATION_VARIANCE 90
#define MEAN_FALL_TIME 10
#define FALL_TIME_VARIANCE 5

@interface LDViewController ()
@property (nonatomic, strong) NSTimer *leafTimer;
@end

@implementation LDViewController

-(void) animateLeaves:(id) sender {
    int piecenum = (arc4random()%NUMBER_OF_LEAF_TYPES) + 1;
    int screenWidth = self.view.frame.size.width;
    int screenHeight = self.view.frame.size.height;
    UIImage *image = [UIImage imageNamed:[NSString stringWithFormat:@"leaf%d", piecenum]];
    UIImageView *newPiece = [[UIImageView alloc] initWithImage:image];
    int startX = arc4random()%screenWidth;
    int endX = startX + ((arc4random()%DRIFT_X_VARIANCE) - (DRIFT_X_VARIANCE / 2));
    newPiece.center = CGPointMake(startX, -image.size.height);

    [self.view insertSubview:newPiece atIndex:0];
    int time = ((arc4random()%FALL_TIME_VARIANCE) + (MEAN_FALL_TIME - (FALL_TIME_VARIANCE / 2)));
    [UIView animateWithDuration:time
                     animations:^(void) {
        float rotation =
                 DegreesToRadians((arc4random()%ROTATION_VARIANCE) - (ROTATION_VARIANCE / 2));
        newPiece.transform = CGAffineTransformMakeRotation(rotation);
        newPiece.center = CGPointMake(endX, screenHeight + image.size.height);
    }completion:^(BOOL finished) {
        [newPiece removeFromSuperview];
    }];

}

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.leafTimer = [NSTimer scheduledTimerWithTimeInterval:LEAF_DROP_INTERVAL
                                                      target:self
                                                    selector:@selector(animateLeaves:)
                                                    userInfo:nil
                                                     repeats:TRUE];
}

Walking through the code, lines 3-8 define the parameters that control the look of the animation. The LEAF_DROP_INTERVAL is how often a new leaf will start to fall, in seconds. DRIFT_X_VARIANCE is how far to the left or right of the start X position the end position can vary. A variance of 40 means that the leaf can end up -20 to +20 pixels horizontally offset from the start. The rotation variance is how much (in degrees) the leaf can rotate as it falls, 90 means -45 to +45 degrees of rotation. The MEAN_FALL_TIME is the average time a leaf will take to fall, and the FALL_TIME_VARIANCE is the range in seconds that it can vary from this. In this case, leaves can take 5 to 15 seconds to fall across the screen.

Skipping down to line 43, our viewDidLoad starts up a timer that will fire according to the interval defined in our LEAF_DROP_INTERVAL constant, calling the animateLeaves selector on the view. Once the timer fires, the animateLeaves method begins by picking a random leaf image, and creating a UIImageView using it. A random starting X position is generated on line 22, and the end X position on line 23. The image view is positioned at the start X, with the Y above the top of the screen, and the image view is inserted into the view at index 0, which will place it in back of everything else. This makes the label appear to be in front of the leaves. After calculating the random fall time on line 27, we’re ready for the animation itself.

The code uses the standard animateWithDuration method. All we need to do in the body of the call is to specify the end state for our image. In this case, we place it off the bottom of the screen with the end X position, and rotate it by the random rotation value. Finally, after the animation completes, we remove the image from the view, so it doesn’t continue to consume memory resources.

The only other thing that should be done is to have whatever code exists on the page terminate the timer, so that it doesn’t continue to try dropping leaves once the page is gone. If there’s a chance the page might be returned to, the viewDidAppear method should restart it.

leaves

tags: , ,