How To Use UIView Animation Tutorial

http://www.raywenderlich.com/2454/how-to-use-uiview-animation-tutorial


If you're new here, you may want to subscribe to my RSS feed or follow me on Twitter. Thanks for visiting!

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

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

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 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’ll learn how to use UIView animations in both the standard way and the newest iOS6 manner, learn how to chain UIView animations, and learn how to tell the position of views while animations are running.

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

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. Pick up where 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.

drag images and sounds to the project

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 find ‘Mode’ property in the Attributes inspector in the right pane, after you select the image view.

Now time for code, you need to declare properties for the new two image views in order to move them from your code, 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 (assign) IBOutlet UIImageView *basketTop;
@property (assign) 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, 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 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 assign here, just to make things easier for us (i.e. no need to release the instance vars since the class won’t be retaining them).

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)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
 
    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:^{
        basketTop.frame = basketTopFrame;
        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 Interface Builder, and note that since the Image Views are taking up the whole screen, it’s going to be hard to keep track of things. To deal with this, let’s name the image views that are already there.

Click on the top image view, and in the Identity Inspector, set the name of the image view to “Basket Top”. Note this doesn’t affect anything in code, it just makes it easier to identify your views in the XIB. Repeat with the bottom image view, setting the name to Basket Bottom. At this point, your XIB should look like the following:

Using names in a XIB

Now, select the two Image Views in Interface Builder and go to Edit\Duplicate. Rename the two new views to “Napkin Top” and “Napkin Bottom”, and drag them 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. Finally, select each napkin image view individually, move it to the right spot, and set the image to XXX. At this point, your XIB should look like the following:

Adding napkins UIImageViewControllers to the XIB

Now that you have the new image views in your XIB, 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 PicnicViewController.h:

@property (assign) IBOutlet UIImageView *napkinTop;
@property (assign) IBOutlet UIImageView *napkinBottom;

Save your header, then in Interface Builder, control-click “File’s Owner” to bring up the menu of outlets, and connect the two napkin outlets to their appropriate image views.

Switch to PicnicViewController.m and make the following modifications:

// At top, under @implementation
@synthesize napkinTop;
@synthesize napkinBottom;
 
// At bottom of viewDidLoad
CGRect napkinTopFrame = napkinTop.frame;
napkinTopFrame.origin.y = -napkinTopFrame.size.height;    
CGRect napkinBottomFrame = napkinBottom.frame;
napkinBottomFrame.origin.y = self.view.bounds.size.height;
 
[UIView animateWithDuration:0.7
                      delay:1.2
                    options: UIViewAnimationCurveEaseOut
                 animations:^{
                     napkinTop.frame = napkinTopFrame;
                     napkinBottom.frame = napkinBottomFrame;
                 } 
                 completion:^(BOOL finished){
                     NSLog(@"Done!");
                 }];

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

Double door effect with UIView animation

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 properties (center and transform) and using some animation chaining!

But first – let’s add the inside of the picnic basket! Open up PicnicViewController.xib 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. You also might want to set the name of the Image View to “Contents” so it’s easy to identify.

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 Contents View, and set the image to bug.jpg. Set its frame to X 160, Y 185, width 135, height 142 in the Size Inspector. You also might want to set the name of the Image View to “Bug” so it’s easy to identify.

At this point, your XIB should look like the following:

Final XIB Layout

Next add a property for the new pest image view in PicnicViewController.h:

@property (assign) IBOutlet UIImageView *bug;

Save your header, switch back to Interface Builder, and connect the bug image view to the outlet in the same way you did with the other outlets earlier in this tutorial.

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

// At top, under @implementation
@synthesize bug;
 
// Add two new methods
- (void)moveToLeft:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
 
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:1.0];
    [UIView setAnimationDelay:2.0];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(faceRight:finished:context:)];
    bug.center = CGPointMake(75, 200);
    [UIView commitAnimations];
 
}
 
- (void)faceRight:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
 
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:1.0];
    [UIView setAnimationDelay:0.0];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(moveToRight:finished:context:)];
    bug.transform = CGAffineTransformMakeRotation(M_PI);
    [UIView commitAnimations];
 
}
 
- (void)moveToRight:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
 
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:1.0];
    [UIView setAnimationDelay:2.0];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    [UIView setAnimationDidStopSelector:@selector(faceLeft:finished:context:)];
    bug.center = CGPointMake(230, 250);
    [UIView commitAnimations];
 
}
 
- (void)faceLeft:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context {
 
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:1.0];
    [UIView setAnimationDelay:0.0];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    [UIView setAnimationDidStopSelector:@selector(moveToLeft:finished:context:)];
    bug.transform = CGAffineTransformMakeRotation(0);
    [UIView commitAnimations];
 
}
 
// Call the method at the bottom of viewDidLoad
[self moveToLeft:nil finished:nil context:nil];

First, note that we’re using the pre-iOS4 method of running the animations here, instead of the iOS4+ method we’ve been using lately. This is because there is a problem you use the iOS4+ method – the view doesn’t respond to touches while the animations are running! I’m not sure if this is a bug or I’m doing something wrong, but either way we need touches later on in this tutorial so we’ll revert to the pre-iOS4 method.

Update: Deian from the comments section has pointed out that you can enable UIView to continue to respond to touches in the iOS4 method by using UIViewAnimationOptionAllowUserInteraction in the options, w00t!

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 continue.

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 PicnicViewController.m first:

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
 
    UITouch *touch = [touches anyObject];
    CGPoint touchLocation = [touch locationInView:self.view];
 
    CGRect bugRect = [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’ll talk more about layers and what this all means in a future tutorial, but for now all you have to do is replace the line that gets the bugRect with this:

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

Now you will get the correct frame of the bug even when it’s moving! Mwuahaha… you know what’s coming next! First add the following instance variable to PicnicViewController.h:

bool bugDead;

Then make the following modifications to PicnicViewController.m:

// At the beginning of faceLeft, moveToRight, faceRight, and moveToLeft:
if (bugDead) return; 
 
// At bottom of touchesBegan
bugDead = true;
[UIView animateWithDuration:0.7 
                      delay:0.0 
                    options:UIViewAnimationCurveEaseOut
                 animations:^{                              
                     bug.transform = CGAffineTransformMakeScale(1.25, 0.75);
                 } 
                 completion:^(BOOL finished) {  
                     [UIView animateWithDuration:2.0 
                                           delay:2.0 
                                         options:0
                                      animations:^{                      
                                          bug.alpha = 0.0;
                                      } completion:^(BOOL finished) {
                                          [bug removeFromSuperview];
                                          bug = nil;
                                      }];                 
                 }];

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 and sets it to nil.

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, right click on the Frameworks group, and choose Add\Existing Frameworks…. Choose AudioToolbox.framework, and click Add.

Then mad the following modifications to PicnicViewController.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((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 tutorial.

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?


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值