UIView Tutorial for iOS: How To Use UIView Animation

mtrabelsi

This post is also available in: Japanese

Use animation to see what's inside this picnic basket!

Use animation to see what's inside this picnic basket!

Update 8/7/14: This tutorial is now fully updated for iOS 8 and Swift, check it out!

One of the coolest things about iPhone apps is how animated many of them are. You can have views fly across the screen, fade in or fade, out, rotate around, and much more!

Not only does this look cool, but animations are good indicators that something is going on that a user should pay attention to, such as more info becoming available.

The best part about animation on the iPhone is that it is incredibly easy to implement programmatically! It’s literally just a couple lines of code and you’re up and running.

In this UIView tutorial, you’ll get a chance to go hands-on with UIView animation to create a neat little app about going on a picnic. The picnic basket opens in a neat animated way, and then you get to look what’s inside – and take decisive action!

In the process, you will learn several stuff, among other, how to use UIView animations in both the standard way and the newest iOS 6 manner. Also you will be learning how to chain UIView animations and how to manage animations and layout positions and size in both portrait and landscape orientations thanx to the auto layout feature.

So grab your picnic basket and let’s get started!

Introduction to UIView Animation

Just so you can appreciate how nice and easy UIView animation is, bear in mind that you will need to perform few steps in oder to animate a view moving across the screen in case iOS didn’t provide you with built-in animation support:

  • Schedule a method to be called in your app, for every frame.
  • Every frame, calculate the new position of the view (x and y), based on the desired final destination, the time to run the animation, and the time run so far.
  • Update the view’s position to the calculated position by running the desired animation.

That’s not a ton of work, but it’s a bit annoying that it might make you think twice about implementing an animation. Plus, it gets a lot more complicated to keep track of, the more animations you do.

But don’t worry – animations are extremely easy to use in iOS! There are certain properties on views, such as the view’s frame (its size and position), alpha (transparency), and transform (scale/rotation/etc.) which have built-in animation support thanks to the block objects. So instead of having to do all of the above, you simply:

  • Set up an animation, specifying how long it should run and a few other optional parameters.
  • Set an animatable property on a view, such as its frame, and start the animation running.
  • That’s it – UIKit will take over handle the calculations and updates for you!

So let’s dive in and see what this looks like in code, by creating an animation of a picnic basket opening when you start up the app.

An Opening Picnic Basket

Open up Xcode, go to “File\New\Project…”, choose iOS\Application\Single View Application, and click ‘Next’. Name the project Picnic and fill in the other fields, select iPhone as the device target and make sure ‘Use Storyboards’ and ‘Use Automatic Reference Counting’ are checked, then click Next. Choose a place to save your project and click Create.

Next, download a copy of some images and sounds made by Vicki that you’ll need for this project. Unzip the file and drag all contained files to a new group of your project. Verify that “Copy items into destination group’s folder (if needed)” is checked, and click Finish.

Now, you need to set up the user interface, you will start from the outsider element. As of the first component, you need to set the picnic basket door which will be moved away later to open the basket and crash the bad bug.

From the project navigator, click on MainStoryboard.storyboard to open it in the storyboard editor. Drag two UIImageViews to the view controller, one on top filling up about half the space, and one on the bottom filling up the bottom half. Set the top image view to door_top.jpg (and set View\Mode to Top), and the bottom image to door_bottom.jpg (and set View\Mode to Bottom), and resize the image views until they look OK, as shown below.

basket door

P.S: You may need to disable Autolayout feature for the sake of this UIView tutorial. Autolayout manages your application look on both landscape and portrait orientations and is an entire subject we cannot get it in details here, however, you can learn more about it in this site tutorial.

Go ahead, select MainStoryboard.storyboard, under File inspector, uncheck “Use Autolayout”.

Now time for code, you need to declare properties for the two new image views in order to animate them off later. So click on ViewController.m to open it in the source code editor and add two properties for the image views:

#import "ViewController.h"
 
@interface ViewController ()//This is a class extension
 
@property (nonatomic,weak) IBOutlet UIImageView *basketTop;
@property (nonatomic,weak) IBOutlet UIImageView *basketBottom;
 
@end
 
@implementation ViewController
 
- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
}
 
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}
 
@end

You may be wondering why you didn’t create instance variables for these properties. Well, Xcode 4.5 compiler (called LLVM) has immensely evolved to help you optimize your code. What does this mean is that you no longer need to create instance variable backing the property, Xcode will take care of that. Guess what.. you no longer need to synthesize properties either, henceforth Xcode take care of that too. Cool, huh?

But why did you put the properties declarations in the implementation file instead of the interface file? Well, that’s a best practice matter. Your properties belong to ViewController file and will not be used from outside this file (at least for the sake of this UIView tutorial), so why shall you declare them in the interface file? instead, you put them on a so called ‘class extension’.

Also note that the properties are marked as weak references. For previous iOS versions, you used to declare outlets as strong and set them to nil in viewDidUnload method, but since the latter method is deprecated in iOS6, you just assign them weak references so that they become nil automatically when the view gets deallocated.

Now save the implementation file, go back to MainStoryboard.storyboard and select the view controller. From the Connections Inspector, select the circle next to basketBottom and connect it to the bottom image view, repeat the same for basketTop outlet and connect it to the top image view.

Now that you’ve connected the views you’ve created in Interface Builder to their properties, you can animate them to open the basket when the view first appears. Switch back to ViewController.m and make the following modifications:

- (void)viewDidAppear:(BOOL)animated
{
 
    CGRect basketTopFrame = self.basketTop.frame;
    basketTopFrame.origin.y = -basketTopFrame.size.height;
 
    CGRect basketBottomFrame = self.basketBottom.frame;
    basketBottomFrame.origin.y = self.view.bounds.size.height;
 
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:0.5];
    [UIView setAnimationDelay:1.0];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
 
    self.basketTop.frame = basketTopFrame;
    self.basketBottom.frame = basketBottomFrame;
 
    [UIView commitAnimations];
}

Here you first calculate where to move the top and bottom images to – basically you move them just offscreen.

Then comes the fun part – the animation code! It uses beginAnimations:context to start an animation block, and then sets three parameters. The animation is set with a duration of 0.5 seconds, and not to start until 1 second in (so you can enjoy the pretty basket for a second), and the animation curve is set to ease out (so the animation goes a little bit slower at the end).

Then the frames of the two image views are set to their final destinations, and you call commitAnimations to start the animations. UIKit takes over from there and runs a neat animation of your basket opening up.

Compile and run the code to try it out for yourself – pretty easy to get such a neat effect, eh?

Basket Halfway Open

Alternate method for iOS 4+

The animation code as-written above is pretty easy, but there’s an even easier method on iOS 4. In iOS 4, UIView has some new methods you can use that use blocks:

  • animateWithDuration:animations:
  • animateWithDuration:animations:completion:
  • animateWithDuration:delay:options:animations:completion:

These methods let you put all of the parameters that you would have had to write multiple lines for in a single call. And the best part is it makes it really easy to call a block of code when the animation finishes too, via the completion parameter.

By the way, don’t be scared of blocks! Blocks have a funky syntax to get used to at first, but they can really help make your code a lot more terse and easier to read, and help keep related blocks of code closer together. Try them out and you’ll get used to them in no time!

So let’s give this a try. Go ahead and replace the code starting at [UIView beginAnimations…] and ending with [UIView commitAnimations] with the following alternative method:

    [UIView animateWithDuration:0.5
                          delay:1.0
                        options: UIViewAnimationCurveEaseOut
                     animations:^{
                         self.basketTop.frame = basketTopFrame;
                         self.basketBottom.frame = basketBottomFrame;
                     } 
                     completion:^(BOOL finished){
                         NSLog(@"Done!");
                     }];
}

This uses one of the new methods, specifying a block for the animations to run, and a block of code to be called when the animations complete. This does exactly what the code used to do, except it’s more terse, and we have an easy way to know when the animations are done.

So compile and run the code, and you should see the basket slide open as usual, but also see a console message when the basket is fully opened!

By the way – if you want to use the pre-iOS4 method and get notice when the animation completes, you can do that with the setAnimationDidStopSelector method.

A Second Layer of Fun

When you go out on a picnic, you usually don’t just throw your food straight into the basket – instead you put a little napkin on top to shield the food from pesky infiltrators. So why not have a little more fun with animations and add a napkin layer to open as well?

Go back to MainStoryboard.storybord, and from the View Controller Scene, select the two UIImageViews you worked with so far.

Duplicate UIImageViews

With the UI elements selected, go to Edit\Duplicate. Select each view and from the Attributes inspector, set the image property to fabric_top.png and fabric_bottom.png. Drag the two new views under the UIView so they are children. Move the views above the two Basket views, since views are listed from bottom->top and you want the napkin to be below the basket. At this point, your view scene should look like the following:

Add Napkin top and bottom views

Now that you have the new image views in your View Controller Scene, see if you can animate this yourself based on everything you’ve learned! The goal is to make the napkin move off screen also, but start moving slightly after the picnic basket starts moving. Go ahead – you can always check back here if you get stuck.


…waiting…

…waiitng…

…waiting…

Tomato-San is angry!

Tomato-San is angry!

What?! Are you still reading here?! You can do it – go ahead and try! :]

The Solution

In case you had any troubles, here’s the solution.

First add two new properties to your class extension in ViewController.m, as you did for the basket outlet properties:

@property (nonatomic,weak) IBOutlet UIImageView *napkinTop;
@property (nonatomic,weak) IBOutlet UIImageView *napkinBottom;

Save your file, then switch to the View Controller Scene. Select your view controller, and from the Outlets pane in the Connections inspector, connect the two napkin outlets to their appropriate image views.

Switch to ViewController.m and make the following modifications:

    // At bottom of viewDidAppear
    CGRect napkinTopFrame = self.napkinTop.frame;
    napkinTopFrame.origin.y = -napkinTopFrame.size.height;
    CGRect napkinBottomFrame = self.napkinBottom.frame;
    napkinBottomFrame.origin.y = self.view.bounds.size.height;
 
    [UIView animateWithDuration:1
                          delay:1.5
                        options: UIViewAnimationCurveEaseOut
                     animations:^{
                         self.napkinTop.frame = napkinTopFrame;
                         self.napkinBottom.frame = napkinBottomFrame;
                     }
                     completion:^(BOOL finished){
                         NSLog(@"Done!");
                     }];

No big changes comparing with the basket views animations you already worked with, just note that we assign bigger values to animateWithDuration and delay parameters to watch the nice napkins moving off the screen.

Compile and run your code, and you should see the basket open in an even cooler manner!

Open basket

How To Chain Animations

So far you’ve just been animating a single property on these UIViews – the frame. Also, you’ve done just a single animation, and then you were done.

However as mentioned earlier in this article, there are several other properties you can animate as well, and you can also trigger more animations to run after one animation completes. So let’s try this out by experimenting with animating two more interesting properties (center and transform) and using some animation chaining!

But first – let’s add the inside of the picnic basket! Open up MainStoryboard.storyboard and drag yet another UIImageView as a subview of the View. Make sure it’s at the top of the list (so it’s underneath everything else) and set the image to plate_cheese.jpg. Resize the image view so it fills up the whole screen.

There’s one more thing you have to add. Somehow, despite all of your precautions, a sneaky bug has made its way into the basket! Add another UIImageView as a subview of the View. Put it right underneath the plate_cheese View, and set the image to bug.png. Set its frame to X 160, Y 185, width 135, height 142 in the Size Inspector.

At this point, your View Controller Scene should look like the following:

Complete View Controller Scene controls

Next add a property for the new pest image view in ViewController.m class extension. You can do so the same way you did with the other outlets, but Xcode allows an easier way to create and connect outlet in one clic. So far, you worked with the Standard editor, switch to the Assistant editor (in the top right pane) and make sure that ViewController.m and MainStoryboard.storyboard are opened side by side like so:

Open two files in the assistant editor

Now from the View Controller Scene, select the bug’s image view, then hold down the ctrl key and drag from the image view in question over into your class extension in ViewController.m.

Connect outlet in the Xcode4 way

In the window that pops up, set the name to bug, the storage to Weak and click connect.

Next switch to ViewController.m, and make the following modifications:

 
// Add four new methods for the bug animation
- (void)moveToLeft:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
 
    [UIView animateWithDuration:1.0
                          delay:2.0
                        options:(UIViewAnimationCurveEaseInOut|UIViewAnimationOptionAllowUserInteraction)
                     animations:^{
                            [UIView setAnimationDelegate:self];
                            [UIView setAnimationDidStopSelector:@selector(faceRight:finished:context:)];
                            self.bug.center = CGPointMake(75, 200);
                        }
                        completion:^(BOOL finished){
                            NSLog(@"Move to left done");
                        }];
 
}
 
- (void)faceRight:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
 
    [UIView animateWithDuration:1.0
                          delay:0.0
                        options:(UIViewAnimationCurveEaseInOut|UIViewAnimationOptionAllowUserInteraction)
                     animations:^{
                         [UIView setAnimationDelegate:self];
                         [UIView setAnimationDidStopSelector:@selector(moveToRight:finished:context:)];
 
                         self.bug.transform = CGAffineTransformMakeRotation(M_PI);
 
                     }
                     completion:^(BOOL finished){
                         NSLog(@"Face right done");
                     }];
 
}
 
- (void)moveToRight:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
 
    [UIView animateWithDuration:1.0
                          delay:2.0
                        options:(UIViewAnimationCurveEaseInOut|UIViewAnimationOptionAllowUserInteraction)
                     animations:^{
                         [UIView setAnimationDelegate:self];
                         [UIView setAnimationDidStopSelector:@selector(faceLeft:finished:context:)];
                         self.bug.center = CGPointMake(230, 250);
 
                         }
                     completion:^(BOOL finished){
 
                        NSLog(@"Move to right done");
                     }];
 
}
 
- (void)faceLeft:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
 
    [UIView animateWithDuration:1.0
                          delay:0.0
                        options:(UIViewAnimationCurveEaseInOut|UIViewAnimationOptionAllowUserInteraction)
                     animations:^{
                         [UIView setAnimationDelegate:self];
 
                         [UIView setAnimationDidStopSelector:@selector(moveToLeft:finished:context:)];
                         self.bug.transform = CGAffineTransformMakeRotation(0);
 
 
                     }completion:^(BOOL finished){
                         NSLog(@"Face left done");
 
                     }];
 
}
 
// Launch the chain of the bug animation at the bottom of viewDidAppear
[self moveToLeft:nil finished:nil context:nil];

First, as for previous animations, we are using block-based animations which is the recommended way in iOS4 and later. Also, you may notice the UIViewAnimationOptionAllowUserInteraction constant in the options parameter, this is because we need the bug view to respond to click event (to get squashed:]) while it’s moving, so that constant allows you to interact with views while they are being animated.

Second, you can see that the way animation chaining is done is by a) using setAnimationDelegate to set the view controller as the delegate, and b) using setAnimationDidStopSelector to set up a callback when the animation finishes. The callback just starts up the next animation, and continues the chain. The chain is move left -> face right -> move right -> face left -> move left again, and so on.

Third, note that we’re moving the bug by using the center property rather than the frame property. This sets where the center of the bug image is, which is a little easier to do than modifying the frame sometimes.

Fourth, note that you can get the right transform to rotate the bug, by using a little helper function called CGAffineTransformMakeRotation. The angle here is in radians, so you use M_PI to rotate 180 degrees.

Compile and run the code, and you should see the bug scurrying about!

Eek!  A bug's in our picnic!

Squash the Bug!

Now it’s the moment I know you’ve been waiting for – it’s time to squash that bug!

But first we have to detect when you’re tapping the bug. Try adding this code to ViewController.m first:

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
 
    UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [touch locationInView:self.view];
 
    CGRect bugRect = [self.bug frame];
    if (CGRectContainsPoint(bugRect, touchLocation)) {
        NSLog(@"Bug tapped!");
    } else {
        NSLog(@"Bug not tapped.");
        return;
    }
 
}

This implements the touch handler, and tries to compare the point tapped to the frame of the bug. Seems like that should work, right? Well if you compile and run, you’ll see something weird: it will appear to work sometimes, but after the bug rotates, if you tapped on the bug it won’t work. However if you click in the area he’s facing, it will say you tapped the bug (which you obviously didn’t) – WTF!

Well, the reason this doesn’t work is because when an animation is running, it only updates the position of the view in something called the “presentation layer”, which is what is shown to screen. We have talked more about layers and what this all means, you can check this tutorial for more in depth, but for now all you have to do is replace the line that gets the bugRect with this:

CGRect bugRect = [[[self.bug layer] presentationLayer] frame];

P.S: Don’t forget to add the QuartzCore framework to your project target (Targets->Linked Frameworks and Libraries), and import the framework at the top of ViewController.m file, as presentationLayer is a method of CALayer class which is one of QuartzCore classes.

Now you will get the correct frame of the bug even when it’s moving :]. You know what’s coming next! First add the following instance variable to ViewController.m class extension:

#import <QuartzCore/QuartzCore.h>
#import "ViewController.h"
 
@interface ViewController (){//This is a class extension
 
 
        bool bugDead;
}

Then make the following modifications to ViewController.m:

// At the beginning of faceLeft, moveToRight, faceRight, and moveToLeft:
if (bugDead) return; 
 
// After NSLog(@"Bug tapped!"); in touchesBegan method
        bugDead = true;
        [UIView animateWithDuration:0.7
                              delay:0.0
                            options:UIViewAnimationCurveEaseOut
                         animations:^{
                             self.bug.transform = CGAffineTransformMakeScale(1.25, 0.75);
                         }
                         completion:^(BOOL finished) {
                             [UIView animateWithDuration:2.0
                                                   delay:2.0
                                                 options:0
                                              animations:^{
                                                  self.bug.alpha = 0.0;
                                              } completion:^(BOOL finished) {
                                                  [self.bug removeFromSuperview];
                                              }];
                         }];

Once the bug is tapped, it sets bugDead to true (so the animation chain stops running), and then uses the iOS4+ method (since we don’t care about touches anymore and this is more terse) so set up a mini-chain of animations.

It first squishes the bug (by applying a scale transform), and then makes the bug fade away (by setting the alpha to 0 after a delay). Finally, when it’s done it removes the bug from its super view.

Compile and run the code, and now you should be able to squash the bug!

One squashed bug!

Gratuitous Sound Effect

This is totally unnecessary, but also totally fun – let’s add a sound effect when we squash the bug!

In XCode, select your project target, and under Linked Frameworks and Libraries, click on the ‘+’ button, choose AudioToolbox.framework, and click Add.

Then made the following modifications to ViewController.m:

// At top of file
#import <AudioToolbox/AudioToolbox.h>
 
// At bottom of touchesBegan
NSString *squishPath = [[NSBundle mainBundle] 
                        pathForResource:@"squish" ofType:@"caf"];
NSURL *squishURL = [NSURL fileURLWithPath:squishPath];
SystemSoundID squishSoundID;
AudioServicesCreateSystemSoundID((__bridge CFURLRef)squishURL, &squishSoundID);
AudioServicesPlaySystemSound(squishSoundID);

And that’s it! Compile and run your code and you can now squash the bug and get major audio satisfaction in the process.

Update: Note if you were to use this in a real app, you should free the sound when you’re done with it. See Jim Murff’s comment for more details! :]

Where To Go From Here?

Here is a sample project with all of the code we’ve developed in the above UIView tutorial.

You can think of additional animations basing on what you learned so far. For example, how about closing the picnic basket after squashing the bug (if you felt disgusted like me once you see the bug playing around the foods :p). You can also change when to open up the basket doors and napkins, so that it will remain closed when you launch the app, until you touch it.

Now that you know the basics of using UIView animations, you might want to take a look at the Animations section in the View Programming Guide for iOS for additional useful info.

Now that you know a bit about UIView animations, you might want to check out the next tutorial about one of the underlying technologies of animation – layers! There are a lot of cool things you can do with layers, but they can be confusing at first, so I wanted to talk about that. This tutorial is also a good segway into Core Animation, which is the technology UIView animation is based upon.

How do you use UIView animation or Core Animation in your projects? I’d love to hear from you!

User Comments

48 Comments

[ 1 , 2 , 3 , 4 ]
  • Hi Ray,

    I am confused as to why you seem to first set the center's x and y point to their negative values, then change them back after the animation is complete. Why not just use UIView's animateWithDuration method?
    moonman239
  • Whoops. I see that you build the app both ways just to cover something that works on OS versions earlier than iOS4.
    moonman239
  • animateWithDuration is much more easier!
    daveztong
[ 1 , 2 , 3 , 4 ]

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in September: iOS 8 App Extensions!

Sign Up - September

RWDevCon Conference?

We are considering having an official raywenderlich.com conference called RWDevCon in DC in early 2015.

The conference would be focused on high quality Swift/iOS 8 technical content, and connecting as a community.

Would this be something you'd be interested in?

    Loading ... Loading ...

Our Books

Our Team

Tutorial Team

  • Dani Arnaout

... 50 total!

Update Team

... 14 total!

Editorial Team

  • John Clem

... 23 total!

Code Team

  • Orta Therox

... 3 total!

Translation Team

  • Zihan Xu
  • Miguel Angel

... 33 total!

Subject Matter Experts

  • Richard Casey

... 4 total!