Chapter 5. Flipping Out and Sweeping Away with Core Animation

In previous chapters, you learned about displaying moving graphics with the UIImageView, and about drawing lines, shapes, paths, and gradients on a UIView with Quartz 2D. These technologies pack a lot of functionality and are essential to making 2D games on the iPhone. You will be using them a lot in your games.

Quartz 2D is actually a Mac OS X technology that has made its way unchanged to the iPhone. Like the Core Foundation and Foundation frameworks, Quartz 2D is a fundamental API in both Mac OS X and iPhone OS. UIKit, while very central to iPhone programming, is a simplified version of Mac OS X's AppKit.

Another iPhone technology with a lot of functionality is Core Animation. Unlike Quartz 2D and UIKit, Core Animation was invented just for the iPhone, and made its way into Mac OS X Leopard after the iPhone was developed. Core Animation's power for making dynamic effects on the iPhone's UI is what gives the iPhone its personality.

Core Animation is the technology behind the slick way views slide as you navigate around in many iPhone apps. It's used to make an application's loading image grow and fade in once you touch the app's icon. It's also behind the application screen shrinking and fading to nothingness once you quit with the home button.

To be clear, the animation part of the name Core Animation has nothing to do with displaying a sequence of images in rapid succession. Core Animation is used for the movement of components across the screen, such as the sliding of photos on and off the screen as you navigate through a photo album in your favorite photo app. It is also used for special effects, such as the curling page in the Maps application. You will use Core Animation to add cool animated effects to your game's UI, or to make a terrific intro screen.

In this chapter, you will learn how to unleash the power of Core Animation in your own games.

Core Animation Sample Project Overview

To get started with Core Animation as soon as possible, we will begin coding from an existing project. This will get us to the important code faster than walking through creating the project in Interface Builder, which is covered in Chapter 3.

To begin, open the sample project called CoreAChicken first stage (provided with this book's source code) in Xcode. Select the Resources group in the Groups and Files pane. Double-click the CoreAChickenViewController.xib file to open it, as shown in Figure 5-1.

The CoreAChickenViewController.xib file opened in Interface Builder, in list view mode, with view and toolbar objects expanded to show their components

Figure 5.1. The CoreAChickenViewController.xib file opened in Interface Builder, in list view mode, with view and toolbar objects expanded to show their components

Figure 5-1 shows the logical composition of the sample app's only view controller. It is composed of a view, which contains a toolbar and two views. The views are called firstView and secondView. The toolbar has a button called animateButton. The firstView is composed of two image views, called theRoad and theChicken. The secondView contains two labels.

Let's look now at the visual composition of the same view controller. If you don't see the window shown in Figure 5-2, double-click the component called View to open it.

The visual layout of the app's main screen as displayed in Interface Builder's view editing window

Figure 5.2. The visual layout of the app's main screen as displayed in Interface Builder's view editing window

In Figure 5-2, you can see the toolbar at the bottom of the screen, with the animateButton component. Above the toolbar are two views stacked on top of one another. The secondView is laid out on top of firstView, but secondView actually has its hidden property set. This makes Interface Builder show secondView as half transparent in this window. Because secondView is shown half transparent, you can see firstView behind it, and the image views called theChicken and theRoad laid out in firstView.

Keep in mind that even though secondView is shown half transparent in Interface Builder, when the app runs, it will not be visible because the hidden property is set. When the app first runs, firstView will be visible, and the first animations will be run on it. We will change secondView's hidden property and animate it in some of the code we will write in this chapter, to create fades and other types of animated transitions between the two views.

The code we'll be concerned with in this chapter is an animate method, which will demonstrate Core Animation routines. It's in the CoreAChickenViewController.m file, which you can open in Xcode, from the Classes group. Listing 5-1 shows the code, which is appears around line 20 in the file.

Example 5.1. The code that powers the animateButton in the sample app (in CoreAChickenViewController.m)

- (IBAction)animate {
    static int counter = 0;

    switch (++counter) {
        default:
            counter = 0;
            break;
    }
}

The code begins by defining an IBAction method named animate. IBAction methods are connected to a UI component in Interface Builder. They are called when a button gets clicked or when the user triggers some other event. The animate method is connected to the animateButton displayed on the toolbar at the bottom of the app's main view.

In this first stage of the code, the method doesn't do much. It declares a static integer variable called counter, increments it, and checks it in a switch statement. There's only one case in the switch, the default case, which resets the counter back to 0.

In the course of this chapter, we will code the animate method to play through a sequence of Core Animation routines—a different routine each time the animateButton is clicked. When the last routine plays, the counter is reset to start again from the first routine.

You will be adding switch cases and animation routines to this method throughout this chapter. I will walk you through the code and explain the Core Animation APIs used.

Animating UIViews

The easiest way to use Core Animation is to take advantage of its close integration with UIKit on the iPhone. In creating UIKit, Apple engineers added basic animation functionality that uses Core Animation behind the scenes, simplifying its application for the most common use cases. We will examine the simpler APIs in this section, and explore the more complex (and more flexible) APIs in later sections.

The UIView class provides class methods for the programming of animated transitions between different property values in your UI components. You use these class methods to begin an animation block, to set animation properties such as animation duration, and to commit the animation block.

You begin an animation block by calling the UIView class method named beginAnimations:context:. This method takes two parameters: an NSString instance used to identify the animation block and a context. Both parameters are optional, but their purpose is to help you identify which animation block called your animation delegate methods (as you'll see in the examples throughout this chapter).

The context parameter is a void pointer, meaning that you can pass anything you want in this parameter. You can use this context pointer to pass a data structure or instance of a class that your animation delegate may need to analyze or modify in regard to the animation starting or finishing.

Once you have begun an animation block, you close it by calling the UIView class method named commitAnimations. Committing the animation block will make Core Animation play the animation in a separate thread from your UI event handling.

Animation blocks can be nested, which means you can begin one animation block within another. Each animation block can have its own animation properties, such as its own animation identifier and duration. But the nested animation blocks won't start playing until the outer animation block is committed. Here's how animation blocks can be nested:

[UIView beginAnimations:@"animation1" context:nil];

// component1 properties animated here

[UIView beginAnimations:@"animation2" context:nil];

// component2 properties animated here

[UIView commitAnimations];

[UIView commitAnimations];

As you may remember from Chapter 3's introduction to UIKit, UIKit components such as UIImageView, UILabel, and UIButton are derived from UIView. By deriving from a common class, they have many properties in common, such as position, size, and alpha. Core Animation can animate the following five UIView properties:

  • frame: The component's frame rectangle, in terms of its superview.

  • center: The coordinates of the component's center point, in terms of its superview.

  • bounds: The view's bounding rectangle, in coordinates inside the component.

  • transform: The transform applied to the component, relative to the center of the bounding rectangle.

  • alpha: The component's alpha value, which represent its opacity.

Changing any of these properties within an animation block will make Core Animation play an animation in which the value of the property or properties change from their initial value just before the start of the animation block to the final value set within the animation block.

For example, let's say we have a UIImageView component with an initial alpha property value of 1. We start an animation block, set the value of the component's alpha property to 0, and then commit the animation to close the animation block. This will make Core Animation play an animation in which the opacity of the component changes from fully opaque to fully transparent.

That's enough talk, let's look at some code to make these concepts become more concrete. Listing 5-2 shows the animate method, expanded to support a total of ten animations. Replace the current version of the animate method in the CoreAChickenViewController.m file with this one.

Example 5.2. The expanded animate method, with ten animations (in CoreAChickenViewController.m)

- (IBAction)animate {
    static int counter = 0;

    switch (++counter) {
        case 1:
            [self viewAnimation1];
            break;
        case 2:
            [self viewAnimation2];
            break;
        case 3:
            [self viewAnimation3];
            break;
        case 4:
            [self viewAnimation4];
            break;
        case 5:
            [self viewAnimation5];
            break;
        case 6:
            [self viewAnimation6];
            break;
        case 7:
            [self viewAnimation7];
            break;
        case 8:
            [self viewAnimation8];
            break;
        case 9:
            [self viewAnimation9];
            break;
        default:
            [self viewAnimation10];
            counter = 0;
            break;
    }
    animateButton.enabled = NO;
}

In Listing 5-2, the animate method's switch statement has been modified with nine new case statements. Each case statement, including the default one, calls a different method. The methods are called viewAnimation1 through viewAnimation10.

You may note that at the end of the animate method, the animateButton is disabled. This is so that the user cannot trigger another animation before the current animation is completed. Animation blocks cancel each other out unless they are nested. If the animationButton is not disabled between animations, and the user clicks the button while one animation is playing, the current animation will be canceled as the next animation starts, and this will look wrong. As you'll see in the rest of the code to follow, the animateButton is enabled again at the end of each animation.

Let's now walk through these view animation methods.

Simple Movement

Listing 5-3 shows the code for method viewAnimation1.

Example 5.3. The viewAnimation1 method

- (void)viewAnimation1 {
    // Reset properties to default values
    theChicken.transform = CGAffineTransformIdentity;
    theChicken.frame = CGRectMake(15, 144, 62, 90);

    [UIView beginAnimations:@"viewAnimation1" context:theChicken];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:
        @selector(animationDidStop:finished:context:)];

    theChicken.frame = CGRectMake(15, 330, 62, 90);

    [UIView commitAnimations];
}

The first line of code in the viewAnimation1 method resets theChicken's properties to default values. The transform property controls rotation, scaling (size changes), and translation (position changes) using a set of functions that will be discussed in the "UIView Transforms" section later in this chapter. The CGAffineTransformIdentity constant resets the property to a value that essentially means "no transform." After resetting the transform, the code sets theChicken's frame property to the starting position for this animation.

The next line of code starts an animations block with the animation identifier viewAnimation1 and theChicken image view as the context parameter.

Following that, the code sets the animation delegate:

[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(animationDidStop:finished:context:)];

The animation delegate receives notifications when the animation is about to start and when the animation has stopped through two selectors you can assign. We assign the selector named animationDidStop:finished:context: to be called when the animation stops. Setting the delegate and selectors is optional. But in this sample project, we must always set a delegate and delegate method for stopping the animation, because we need to reenable the animateButton at the end of each animation. You can also assign a selector to be notified when the animation is about to start, if you need that functionality, by calling the UIView class method setAnimationWillStartSelector: and passing the appropriate selector.

In the next line of code in the viewAnimation1 method, we change the frame of the image view theChicken:

theChicken.frame = CGRectMake(15, 330, 62, 90);

The initial position of theChicken's in the Interface Builder .xib file is at coordinates 15 and 144 for the x and y values. Its initial size is a width of 62 and a height of 90. So this line of code simply moves theChicken down the screen to y coordinate 330.

The last line of code in the viewAnimation1 method commits the animation, closing the animation block:

[UIView commitAnimations];

This makes Core Animation play an animation that moves theChicken down the screen in 0.2 second, to stand at the top of the toolbar. The duration of 0.2 second is the default of a UIView animation, unless you set a different duration using the UIView method setAnimationDuration:.

Once the animation stops, Core Animation calls the method we set earlier for notifications that the animation ended. Listing 5-4 shows the code for this delegate method.

Example 5.4. The animationDidStop:finished:context: animation delegate method

- (void)animationDidStop:(NSString *)theAnimation finished:(BOOL)flag
context:(void *)context
{
    animateButton.enabled = YES;
}

The signature for this method is the standard signature for the animationDidStop delegate. You can call the method anything you want, but if the method takes parameters, the parameter types must match this signature, or the method may cause a crash.

The first parameter must be a pointer to an NSString instance. It will be set to the value you passed for the animation name in the beginAnimations:context: call for this animation. The second parameter, if there is one, must be a Boolean flag. The value passed will be YES if the animation ran to completion or NO if it was interrupted. The current animation can be interrupted by another animation block being committed before the current animation plays out.

The last parameter to the delegate method must be a void pointer. It will be set to the value of the context parameter passed in the beginAnimations:context: for the current animation calling the animation delegate.

Animation Curves

Listing 5-5 shows the method viewAnimation2 and its animation stop delegate method.

Example 5.5. The viewAnimation2 method and its animation stop delegate method

- (void)viewAnimation2 {
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:1];
    [UIView setAnimationDelegate:selfs];
    [UIView setAnimationDidStopSelector:@selector(animationDidStop)];
theChicken.frame = CGRectMake(15, 144, 62, 90);

    [UIView commitAnimations];
}

- (void)animationDidStop {
    animateButton.enabled = YES;
}

In this listing, the animation block begins with no parameters set for the animation identifier and for the context. In the next line of code, we set the animation duration to 1 second by calling the setAnimationDuration: class method.

Rather than set the animationDidStop selector to the standard signature, we pass in a selector with no parameters. This has no danger of causing a crash, as any parameters passed by the caller will be ignored.

After setting the delegate and animation stop selector, the code sets the frame for theChicken image view back to the original x and y coordinates of 15 and 144. Then we commit the animation to close the animation block.

When this animation plays, theChicken image view slides back up to the original coordinates over the length of 1 second. If you look carefully, you will see the animation starts moving the image view slowly, picks up speed as it progresses, and then slows down as the animation is about to end. This is the default animation curve used by Core Animation.

When making an animation, the result can look rather mechanical if the animator just changes the movement of the animated component in an evenly spaced manner between frames. Through decades of study and practice in the art of animation, professional animators have learned that there are ways to animate a scene and add a touch of variation to their work. These variations make the animation look more interesting than just moving something from position A to B in a straight line.

Animation curves are a property of an animation you can set to tell Core Animation to apply the changes to the component you want to animate in an uneven manner, to add these variations to the animated result. These curves modify how Core Animation processes the animation. For example, it may begin slowly and pick up acceleration as the animation progresses, or it might start by moving rapidly and then slow down as it reaches the end.

You set the animation curve by calling the UIView class method setAnimationCurve:, passing one of the following curve constants:

  • UIViewAnimationCurveEaseInOut: The default animation curve. The animation begins slowly, accelerates until the middle of the animation's duration, and then slows down again until completion.

  • UIViewAnimationCurveEaseIn: The animation begins slowly and accelerates until the end.

  • UIViewAnimationCurveEaseOut: The animation begins fast but slows down until it reaches the end.

  • UIViewAnimationCurveLinear: The animation runs at a constant speed.

So far, you've learned that animations have a default duration of 0.2 second and that they have a default animation curve that makes the animation start slowly, pick up speed for about half the duration, and then slow down until it stops for the other half of the duration. You also learned you can set delegate methods to be notified when the animation is about to start and when it has come to an end.

Reverse and Repeat

The viewAnimation3 method, shown in Listing 5-6, demonstrates a few more animation properties.

Example 5.6. The viewAnimation3 method and its animation stop delegate method

- (void)viewAnimation3 {
    [UIView beginAnimations:@"viewAnimation3" context:theRoad];
    [UIView setAnimationCurve:UIViewAnimationCurveLinear];
    [UIView setAnimationDuration:0.5];

    [UIView setAnimationRepeatAutoreverses:YES];
    [UIView setAnimationRepeatCount:2];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:
        @selector(theRoadAnimationDidStop:finished:context:)];

    theRoad.alpha = 0.0;
    [UIView commitAnimations];
}

- (void)theRoadAnimationDidStop:(NSString *)theAnimation finished:(BOOL)flag
context:(void *)context
{
    ((UIView *)context).alpha = 1.0;
    animateButton.enabled = YES;
}

The viewAnimation3 method begins by starting an animation block, passing in the string "viewAnimation3" as the animation identifier and the image view called theRoad as the context parameter.

The next method sets the animation curve to linear:

[UIView setAnimationCurve:UIViewAnimationCurveLinear];

This means the animated properties will change at constant speed, from the current value to the value set within the animation block.

The next line of code sets the animation duration to half a second:

[UIView setAnimationDuration:0.5];

The following lines of code set the animation to autoreverse and to play twice:

[UIView setAnimationRepeatAutoreverses:YES];
[UIView setAnimationRepeatCount:2];

The animation will play forward for the duration set in the previous line, play backward for the same duration, and then repeat. This means that the animation plays forward once for half a second, then plays backward for half a second, then plays forward again for half a second, and once again backward for half a second. The animation's total play time will be 2 seconds.

By default, animations don't autoreverse and the repeat count is set to 0, which means there is no repetition. The autoreverse setting is ignored if the repeat count is 0.

The repeat count can be fractional, which means the animation will not play all the way through. If you set the repeat count to 0.5, for example, the animation will stop at the exact halfway point of the duration setting. If you set the repeat count to 6.5, the animation will repeat six times, then stop halfway through a seventh repetition.

The next two lines of code set the animation delegate object and the name of the method that will be called when the animation stops:

[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(theRoadAnimationDidStop:finished:context:)];

The selector's signature matches the standard signature for this delegate method. In this particular delegate method, it means the animation context will be passed as the third parameter to the method. We will make use of the context parameter in the animation delegate method to set the object's property to its final value.

This next line identifies the object and property being changed in the animation:

theRoad.alpha = 0.0;

The image view named theRoad has a current alpha value of 1, making theRoad fully opaque. In this animation, it is being changed to 0, fully transparent.

In the last line of code in this method, the animation block is closed with the committing of the animation to the Core Animation queue.

[UIView commitAnimations];

In essence, the viewAnimation3 method makes theRoad fade out and fade back in twice over a length of 2 seconds.

The next section of code is the animation stop delegate method:

- (void)theRoadAnimationDidStop:(NSString *)theAnimation finished:(BOOL)flag
context:(void *)context
{
    ((UIView *)context).alpha = 1.0;
    animateButton.enabled = YES;
}

In this method, we use the context parameter, which was set to theRoad at the start of the animation block in viewAnimation3. So the delegate method is setting theRoad's alpha property back to 1 now that the animation has ended.

Delay, Ease-In, and Ease-Out

The next view animation methods introduce a few new animation settings. Let's continue with Listing 5-7, which shows viewAnimation4.

Example 5.7. The viewAnimation4 method and its animation stop delegate method

- (void)viewAnimation4 {
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDelay:0.5];
    [UIView setAnimationDuration:1];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseIn];

    [UIView setAnimationRepeatAutoreverses:YES];
    [UIView setAnimationRepeatCount:2];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:
        @selector(resetTheChickenProperties)];

    theChicken.frame = CGRectMake(15, 330, 62, 90);
    [UIView commitAnimations];
}

- (void)resetTheChickenProperties {
    theChicken.transform = CGAffineTransformIdentity;
    theChicken.frame = CGRectMake(15, 144, 62, 90);
    animateButton.enabled = YES;
}

The viewAnimation4 method introduces the concept of the animation delay. You can set a delay for the start of the animation by calling the UIView class method setAnimationDelay:. The animation delay defaults to a value of 0, and the setting accepts a fraction. In this code sample, the animation delay is set to 0.5 second.

The animation curve is set to ease-in. This curve is what you should use for animations simulating bouncing. This animation makes theChicken bounce on the toolbar a couple of times.

Listing 5-8 shows the code for viewAnimation5, which uses the ease-out animation curve.

Example 5.8. The viewAnimation5 method

- (void)viewAnimation5 {
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:1];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(animationDidStop)];

    theChicken.frame = CGRectMake(235, 144, 62, 90);
    [UIView commitAnimations];
}

The ease-out animation curve is perfect for simulating throwing an object up in the air and seeing gravity slow it down over time. It's the opposite of the bouncing effect.

UIView Transforms

The next three animations introduce the transform property. The transform property is used to affect the size (scale), position (translation), and/or rotation of a UIKit component, using the following Core Graphics functions:

  • CGAffineTransformMakeScale: Changes the size of the component. You pass a scale for the width and height of the component. The value of the parameters can be fractions.

  • CGAffineTransformMakeTranslation: Changes the position of the component relative to its original position. You pass units of relative movement along the x and y axes. The x and y units can be fractions.

  • CGAffineTransformMakeRotation: Rotates the component about its center coordinates. You pass the number of radians by which to rotate. The value of the parameter can be a fraction.

  • CGAffineTransformIdentity: Resets the transform property (no transformation).

Note

Radians are the metric equivalent to the degree of rotation of the English system. 180 degrees equal 3.14159 radians. A positive value means clockwise rotation. A negative value means counterclockwise rotation. To convert from degrees to radians, apply the formula degrees * pi / 180.

Listing 5-9 shows the viewAnimation6 and viewAnimation7 methods. These two methods demonstrate scaling and rotation transforms.

Example 5.9. The viewAnimation6 and viewAnimation7 methods

- (void)viewAnimation6 {
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:1];
    [UIView setAnimationCurve:UIViewAnimationCurveLinear];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(animationDidStop)];

    theChicken.transform = CGAffineTransformMakeScale(1.25, 1.25);
    [UIView commitAnimations];
}

- (void)viewAnimation7 {
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:1];
    [UIView setAnimationCurve:UIViewAnimationCurveLinear];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(animationDidStop)];

    CGAffineTransform transform1 = CGAffineTransformMakeScale(1, 1);
    CGAffineTransform transform2 =
        CGAffineTransformMakeRotation(179.9 * M_PI / 180.0);
theChicken.transform = CGAffineTransformConcat(transform1,
                                                   transform2);
    [UIView commitAnimations];
}

In the viewAnimation6 method, we are scaling theChicken to 1.25 times its original size. In the viewAnimation7 method, theChicken is scaled back down to original size. And at the same time, it rotates theChicken clockwise by 179.9 degrees. (Remember that a positive value means clockwise rotation; a negative value means counterclockwise rotation.)

Note

The rotation applied is 179.9 degrees rather than 180 degrees, because Core Animation misinterpreted my intention when I specified 180 degrees. When I specified 180 degrees for the transform, Core Animation interpreted this as a counterclockwise rotation of 180 degrees. Using 179.9 made Core Animation apply the animation as I intended.

The viewAnimation7 method shows that transforms can be combined. The result of two CGAffineTransformMake* functions can be used as inputs to the CGAffineTransformConcat function. This result can be concatenated with a third, and the result concatenated with a fourth, and so on. In this case, we combine two transforms and assign them to the component's transform property.

The viewAnimation8 method, presented in Listing 5-10, shows another way of combining transforms.

Example 5.10. The viewAnimation8 method

- (void)viewAnimation8 {
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:1];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:
        @selector(resetTheChickenProperties)];

    CGAffineTransform transform1 =
        CGAffineTransformMakeTranslation(-220, 0);
    theChicken.transform =
        CGAffineTransformRotate(transform1, 359.9 * M_PI / 180.0);
    [UIView commitAnimations];
}

In the viewAnimation8 method, the code creates a transform, which applies a translation of 220 pixels to the left of the current position. When applied to theChicken at this point in the animations, this transform essentially moves it to the original frame coordinates set in the CoreAChickenViewController.xib file. Before applying this transform, though, we call the function CGAffineTransformRotate, and apply to the translation transform a clockwise rotation transform of 359.9 degrees. This completes the half circle rotation started in viewAnimation7. These combined transforms are applied to theChicken, and then the animation block is closed and the animation committed.

Note

The rotation applied is 359.9 degrees rather than 360 degrees, because Core Animation misinterpreted my intention when I specified 360 degrees. Using 359.9 made Core Animation apply the animation as I intended.

If you want to see a finished working version of the code up to this part of the chapter, you can check out the Xcode project in the folder called CoreAChicken UIView animations, in the source code for this chapter.

UIView Transitions

Besides animating the movement and opacity of UI components such as image views, UIKit's integration of Core Animation also has the ability to animate transitions that occupy the whole screen. These work well to swap areas of the screen out and replace that area with other components.

The API for using UIView transitions is called setAnimationTransition:forView:cache:. This API takes as parameters a transition type, the superview of the components to be transitioned, and a Boolean describing whether you want Core Animation to cache the look of the superview before and after the animation. This caching improves Core Animation's performance.

Four transition types are used by UIView transitions:

  • UIViewAnimationTransitionFlipFromRight: Makes the view contents flip from the right to the left, revealing another view on the other side of the screen. You can see this transition type in the Weather app, when you click the Info disclosure icon at the lower-right corner of the screen.

  • UIViewAnimationTransitionFlipFromLeft: Makes the view contents flip from the left to the right, revealing another view on the other side of the screen. This animation is the opposite of the previous one. In the Weather app, it hides the "back" of the Weather app when you click the Done button.

  • UIViewAnimationTransitionCurlUp: Makes the current view curl up and away like a page on a notepad, revealing another view underneath. You can see this animation play only 50% of the way in the Maps app, when you click the curling page icon to the lower left on the toolbar.

  • UIViewAnimationTransitionCurlDown: Takes the current view and covers it as if a page were uncurling from the top of the screen and revealing another view. This animation is the opposite of the previous one. You can see this by clicking the curling page icon again after revealing the view beneath.

The viewAnimation9 and viewAnimation10 methods use view transitions to hide firstView and show secondView and vice versa. Listing 5-11 shows these methods.

Example 5.11. The viewAnimation9 and viewAnimation10 methods

- (void)viewAnimation9 {
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:1];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseIn];
    [UIView setAnimationTransition:UIViewAnimationTransitionCurlUp
        forView:self.view cache:YES];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(animationDidStop)];

    firstView.hidden = YES;
    secondView.hidden = NO;
    [UIView commitAnimations];
}

- (void)viewAnimation10 {
    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:1];
    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
    [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft
        forView:self.view cache:YES];
    [UIView setAnimationDelegate:self];
    [UIView setAnimationDidStopSelector:@selector(animationDidStop)];

    firstView.hidden = NO;
    secondView.hidden = YES;
    [UIView commitAnimations];
}

The code for viewAnimation9 should look familiar by now. It starts an animation block, sets an animation duration and animation curve, sets delegates and delegate selectors, and commits the animation to close the animation block. The main difference is the use of the setAnimationTransition:forView:cache: method.

[UIView setAnimationTransition:UIViewAnimationTransitionCurlUp
        forView:self.view cache:YES];

This line of code sets the animation transition type to curl up. We specify the transition is for the view controller's view, which contains both firstView and secondView. We also specify that Core Animation should cache the animation for the best performance.

The next relevant lines of code are the hiding and showing of the views:

firstView.hidden = YES;
    secondView.hidden = NO;

We simply hide firstView and show secondView. When the animation is committed, Core Animation whisks firstView away with a page-curl animation about a second long, with secondView shown as firstView animates away.

In the viewAnimation10 method, the relevant code is the line that sets the animation transition type:

[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft
forView:self.view cache:YES];

Here, we specify the transition type that flips the view, with the new view coming from the left side of the screen.

In this view animation, we show the firstView and hide the secondView:

firstView.hidden = NO;
    secondView.hidden = YES;

We are essentially undoing the work of the previous animation, but with a different transition type. When this animation is committed, Core Animation flips the screen from the left, hiding secondView behind firstView, which is again the only visible view.

There are other transition animations the iPhone can do, but these require coding directly with Core Animation APIs. Our exploration of UIView animation methods concludes, and we move on to the more complex APIs offered by Core Animation itself.

Animating Core Animation Layers

I mentioned at the beginning at the UIView animations section that the animation methods provided by UIView were a convenience—a gentle façade provided by the designers of the iPhone SDK to hide the complexity of Core Animation from developers for the most common types of animation effects. In this section, we'll look at how to create animations with Core Animation APIs to do many of the same things you can do with UIView animations.

Core Animation operates on instances of the class CALayer. Every UIKit component has a CALayer instance, accessible through the layer property of the component. It is on this layer that UIKit components draw, and it is the UIKit component's layer that is displayed on screen. When working with UIKit components to create your UI, you typically don't need to concern yourself with the Core Animation layer, unless you want to use the Core Animation APIs directly.

Core Animation layers have many of the same properties as UIView, such as frame, bounds, backgroundColor, and hidden. Like UIView, many of the layer's properties can be animated, including the following (a partial list):

  • position

  • bounds

  • anchorPoint

  • backgroundColor

  • hidden

Implicit Animations

You can trigger an animation on a layer simply by changing one of the layer properties that can be animated. For a UIKit component, you reference the layer property, and then change the property on the layer, as in this example:

theChicken.layer.position = CGPointMake(225, 144);

This is called an implicit animation, and does not require the creation of an animation block. Just change one or several properties, and an animation will be played on the layer, changing the property or properties from the current value to the new value.

Implicit animations have a default duration of 0.25 seconds. You can change the duration of an implicit animation by setting the duration value in the implicit Core Animation transaction, as follows:

[CATransaction setValue:[NSNumber numberWithFloat:1.0]
                             forKey:kCATransactionAnimationDuration];

You change the animation duration value before you modify the property you want to animate. Once changed, the animation will play for the specified duration.

Implicit animations are played with a linear animation curve. Core Animation calls animation curves timing functions (covered in the next section). Here is an example of changing to the ease-in, ease-out timing function:

[CATransaction setValue:kCAMediaTimingFunctionEaseInEaseOut
                             forKey:kCATransactionAnimationTimingFunction];

One thing to remember is that the animation duration and timing function are reset back to default values after the implicit animation plays to the end. Unless you create an explicit transaction and retain it for reuse at later points, the values you give these settings are lost once the implicit transaction is finished playing.

Timing Functions

The Core Animation term timing function seems like an important distinction, but it's just another name for animation curve. You assign the timing function by calling the class method functionWithName: in the CAMediaTimingFunction class. This class method takes as a parameter the type of timing function you want to use. The Core Animation timing functions are as follows:

  • kCAMediaTimingFunctionDefault: Approximates a Bézier timing function with control points (0.0,0.0), (0.25,0.1), (0.25,0.1), (1.0,1.0). It's a curve similar to ease-in, ease-out, but the start and stop slowdowns are not as pronounced.

  • kCAMediaTimingFunctionEaseInEaseOut: Equivalent to UIViewAnimationCurveEaseInOut.

  • kCAMediaTimingFunctionEaseIn: Equivalent to UIViewAnimationCurveEaseIn.

  • kCAMediaTimingFunctionEaseOut: Equivalent to UIViewAnimationCurveEaseOut.

  • kCAMediaTimingFunctionLinear: Equivalent to UIViewAnimationCurveLinear. This is the default timing function for transitions.

kCAMediaTimingFunctionDefault is the default timing function for all Core Animation classes, except transitions. As noted, the default timing function for transitions is kCAMediaTimingFunctionLinear.

Layer Animation Transitions

Now that we've covered the basics of implicit animations on Core Animation layers, let's cover one explicit animation class in the Core Animation arsenal: layer animation transitions. Core Animation provides four transition types:

  • kCATransitionFade: Fades in or out the layer content as it becomes visible or hidden (the default).

  • kCATransitionMoveIn: The layer content slides over the layers underneath.

  • kCATransitionPush: The layers underneath are pushed out by the layer as it slides over.

  • kCATransitionReveal: The layer content is revealed gradually, covering the layers underneath.

Let's expand the animate method to demonstrate these transitions, as shown in Listing 5-12. The code in bold represents the code you need to add or change.

Example 5.12. New code for the animate method in CoreAChickenViewController.m

case 10:
    [self viewAnimation10];
    break;
case 11:
    [self transition1];
    break;
case 12:
    [self transition2];
    break;
case 13:
    [self transition3];
    break;
default:
    [self transition4];
    counter = 0;
    break;

This code adds four new switch case statements and animation methods to the animate method, called transition1 through transition4. Let's look at each of these methods.

Fade

Listing 5-13 shows the transition1 method.

Example 5.13. The transition1 method

- (void)transition1 {
    CATransition *transition = [CATransition animation];
    transition.duration = 0.75;
    transition.timingFunction = [CAMediaTimingFunction
        functionWithName:kCAMediaTimingFunctionLinear];
    transition.delegate = self;

    transition.type = kCATransitionFade;

    [self.view.layer addAnimation:transition forKey:@"transition1"];

    firstView.hidden = YES;
    secondView.hidden = NO;
}

The method begins by creating an instance of a class called CATransition. This is class derived from CAAnimation, the base class for all Core Animation animation APIs. You instantiate a CATransition instance by calling the class method named animation:

CATransition *transition = [CATransition animation];

The CATransition instance has properties for specifying how a transition is going to play—its type and its duration, and other important aspects such as its delegate. The properties of the CATransition instance are similar in nature to the animation settings in UIView, although they may be called differently in Core Animation.

One similarity to a UIView animation is that creating the CATransition instance is like beginning a UIView animation block. One difference is that there is no animation identifier and no context. The class instance itself is used as the identifier in the Core Animation delegate.

One major difference between animations and transitions is that transitions work on the whole screen. UIView animations and Core Animation implicit animations work at the more granular level of properties.

The next lines of code set the animation duration and the timing function:

transition.duration = 0.75;
    transition.timingFunction = [CAMediaTimingFunction
        functionWithName:kCAMediaTimingFunctionLinear];

The duration property and timing functions were discussed earlier. As in UIView animation, transitions play for 0.25 second unless you specify a value.

The next line of code sets the delegate for the transition:

transition.delegate = self;

Unlike the UIView animation delegate, you don't need to set a selector. The transition expects the delegate to have methods named animationDidStart: and animationDidStop:finished:.

The next line of code sets the transition type:

transition.type = kCATransitionFade;

As noted earlier, kCATransitionFade fades in or out the layer content as it becomes visible or hidden.

The next line of code adds the animation to the layer property of the view controller's view.

[self.view.layer addAnimation:transition forKey:@"transition1"];

Until it is removed, this layer will play this transition every time a subview of the view controller's view is animated. If firstView or secondView is hidden, shown, or removed from the superview, or if one of these or another view is added to the superview, Core Animation will trigger this transition. You can remove an animation from a layer by calling the CALayer instance method removeAnimationForKey: and passing in the key with which it was added to the layer.

Adding the animation to the layer is similar in concept to committing a UIView animation block. The exception is that the animation does not begin playing immediately upon being added to the layer. Core Animation transitions and other animation types begin playing when the event handler loop goes idle. The animate method that calls the various animation and transition methods is called by Cocoa Touch to handle the touch event for the animateButton. Therefore, the Cocoa Touch event handler is busy while the animate method processes the touch event. The transition will play once the animate method ends and the flow of execution returns to the Cocoa Touch event handler.

Now that the transition has been added to the layer, the next lines of code tell the fade transition what to operate on:

firstView.hidden = YES;
    secondView.hidden = NO;

The transition will hide the firstView and show the secondView.

In summary, the transition1 method creates a Core Animation transition to hide the firstView and show the secondView using the fade transition type and the linear timing function over a period of 1 second.

In line 6 of Listing 5-13, we set the delegate for this transition. As noted earlier, we don't need to specify a selector for the notification methods. Core Animation expects the delegate to provide at least one of two methods: animationDidStart: and animationDidStop:finished:. Listing 5-14 shows the transition delegate method implemented in the sample project.

Example 5.14. The transition delegate method animationDidStop:finished:

- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag {
    animateButton.enabled = YES;
}

As I mentioned earlier, Core Animation passes the transition object as the first parameter of the delegate methods, in a similar fashion to how it passes the animation identifier as first parameter in a UIView animation delegate method.

Move In, Push, and Reveal

Let's examine the other transition methods in our example, shown in Listing 5-15.

Example 5.15. The transition2, transition3, and transition4 methods

- (void)transition2 {
    CATransition *transition = [CATransition animation];
    transition.duration = 0.75;
    transition.timingFunction = [CAMediaTimingFunction
                        functionWithName:kCAMediaTimingFunctionEaseIn];
    transition.delegate = self;

    transition.type = kCATransitionMoveIn;
    transition.subtype = kCATransitionFromRight;

    [self.view.layer addAnimation:transition forKey:@"transition2"];

    firstView.hidden = NO;
    secondView.hidden = YES;
}

- (void)transition3 {
    CATransition *transition = [CATransition animation];
    transition.duration = 0.75;
    transition.timingFunction = [CAMediaTimingFunction
                        functionWithName:kCAMediaTimingFunctionEaseOut];
    transition.delegate = self;

    transition.type = kCATransitionPush;
    transition.subtype = kCATransitionFromLeft;

    [self.view.layer addAnimation:transition forKey:@"transition3"];

    firstView.hidden = YES;
    secondView.hidden = NO;
}

- (void)transition4 {
    CATransition *transition = [CATransition animation];
    transition.duration = 0.75;
    transition.timingFunction = [CAMediaTimingFunction
                        functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    transition.delegate = self;

    transition.type = kCATransitionReveal;
    transition.subtype = kCATransitionFromRight;

    [self.view.layer addAnimation:transition forKey:@"transition4"];

    firstView.hidden = NO;
    secondView.hidden = YES;
}

These other transition methods are nearly identical to transition1. They differ by setting different timing functions and transition types. Another key difference is that these methods also set a subtype property on the transition. Whereas in the UIView transitions, we had transition types of curl up/curl down and flip left/flip right, in these Core Animation transitions types, we have only types of move in, push, and reveal. The subtype specifies a direction for the transition. as follows:

  • kCATransitionFromTop

  • kCATransitionFromBottom

  • kCATransitionFromLeft

  • kCATransitionFromRight

The fade transition type ignores the subtype because there is only one fade transition. You fade one view out and fade another one in. There is no movement involved.

Core Animation transitions are a cool way to make your UI stand out, by adding interesting animations every time you change screens. UIKit already provides animated transitions as part of the UINavigator functionality, but with Core Animation, you can choose to add your own interesting twist with four types and four subtypes of transitions.

Summary

In this chapter, you learned how to animate UIView instances by changing their properties within an animation block. You learned how to customize the playback of your animations by changing animation block settings such as animation duration, repetition count, and animation delay. The chapter also covered UIView transition effects. You then learned how to use implicit Core Animation animations to perform similar techniques by modifying the Core Animation layer present in all UIKit components. The chapter concluded with examples of Core Animation transitions.

The code for the full implementation of this chapter's example project is provided in a folder called CoreAChicken finished version. I have also provided a version of the brick-breaking game from Chapter 3 that integrates Core Animation, in a folder named IVBricker CoreAnim. When the ball hits a brick in this version of the game, the brick doesn't just fade out to nothing—it also falls off the screen. The fading out of the brick and its falling to the bottom of the screen are implemented using UIView animations.

Core Animation provides a tool set for adding slick transition effects and animations to your game. With the knowledge acquired in this chapter, you can create cool screens, animated intros, cutscenes, and other great game features.

Many more Core Animation APIs are available. If you're interested, you can refer to Apple's excellent iPhone SDK documentation to learn more about Core Animation.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset