3.6. Transition Animations

If there's one thing Apple is well known for, it's a devotion to aesthetics in their user interfaces. The effect of sliding pages left and right gives the user a sense of the flow of data through an application, or a sense of "forward" and "back." Even applications lacking a book type of structure can provide the smooth slide and fade transitions offered by UI Kit. Transitions are animation objects that are attached to an existing view so that when the view is changed, the animation is executed.

Fortunately, most of the work of transitions is done automatically for you when using navigation controllers. There may, however, be some occasions when directly applying a transition can be useful. A framework we haven't yet introduced, named Quartz Core, handles the animation for transitions. Quartz Core performs a number of animations and transformations used to create a number of different effects. Use Quartz Core's animation engine to create a transition animation and apply it to your view. Quartz Core spawns a new thread to take over all of the graphics processing during the transition. The developer needs only to add the desired transition to enhance an existing application.

In order to implement transitions in your application, you'll need to add the Quartz Core framework to your project. Right-click the Frameworks folder in your project, and then choose Add Framework. Navigate to the QuartzCore.framework folder, and then click Add.

3.6.1. Creating a Transition

You'll create transitions as CATransition objects, which are objects belonging to Quartz Core's Core Animation suite of functions. An animation contains properties such as timing function, animation type, and duration. To create an animation, call the class's animation method. An example follows:

#import <QuartzCore/CAAnimation.h>

CATransition *myTransition = [ CATransition animation ];

3.6.1.1. Timing function

The timing function determines how smooth a transition will execute from start to finish. The same animation can be executed at different speeds during the animation. For example, a page flipping animation might begin slow and gain speed at the end of the animation, or vice versa. The following timing functions are available for your animation.

Timing Description
UIViewAnimationCurveEaseInOut Slow at beginning and end of animation
UIViewAnimationCurveEaseIn Slow at beginning, then speeds up
UIViewAnimationCurveEaseOut Animation slows at end of animation
UIViewAnimationCurveLinear Uniform speed throughout duration

Set the timing function by assigning the transition's timingFunction property:

myTransition.timingFunction = UIViewAnimationCurveEaseInOut;

3.6.1.2. Animation types

The animation type defines the type of animation to render. Each animation has both a type and a subtype. The animation's type defines the overall behavior of the transition, while the subtype defines details such as the direction of the transition.

Apple has limited developers' access in the SDK to the animations available, so you can't necessarily add everything you see in Apple's preloaded applications to your own code. The SDK supports the following animation types.

Animation type Description
kCATransitionFade Fade from one view to the next
kCATransitionMoveIn Move the new view in over the old
kCATransitionPush Push the old view out, bring the new view in
kCATransitionReveal Move the old view out revealing the new

The following animation subtypes are supported.

Animation subtype Description
kCATransitionFromRight New view slides from right
kCATransitionFromLeft New view slides from left
kCATransitionFromTop New view slides from top
kCATransitionFromBottom New view slides from bottom

Set the type and subtype by assigning the transition's type and subtype properties:

myTransition.type = kCATransitionPush;
myTransition.subtype = kCATransitionFromLeft;

3.6.1.3. Duration

By default, a standard duration is set for the animation, causing it to run at normal speed. This is generally 0.3 seconds for most animations. If you wish to speed up or slow down the animation, you can manually set the duration time by adjusting the transition's duration property:

myTransition.duration = 0.3;

3.6.2. Attaching a Transition

To effect a transition, add the animation to the view layer to which your animated view is anchored. For example, if you are transitioning between two view controllers, add the animation to the window's layer:

[ [ self.view.superview.layer ] addAnimation: myTransition forKey: nil ];

If you are transitioning from one child view to another within a single view controller, add the animation to the view controller's layer. Alternatively, you might use the internal view object within the view controller instead, and manage your child views as sublayers of the main view:

[ self.view.layer addAnimation: myTransition forKey: nil ];
[ self.view addSubview: newView ];
[ oldView removeFromSuperview ];

If you're using a navigation controller, such as in the PageDemo example, you can add the animation to the navigation controller's view layer to effect a transition between two view controllers:

[ navigationController.view.layer addAnimation: myTransition forKey: nil ];

As the switch is made between views, the animation layer will automatically execute, showing the transition between the two.

3.6.3. FlipDemo: Page-Flipping Transitions

This example works in a similar fashion to the PageDemo example you've already seen. In this example, you'll build a navigation controller on top of an existing view controller. This example displays a left and right navigation bar item, allowing the user to flip between ten pages of text within a single view controller. When this occurs, a new animation is created and added to the parent view to display this transition to the user. A generic UIView object has also been added to act as a parent view within the view controller, so instead of self.view pointing to an individual text view, all ten text views will be created when the class is initialized. The corresponding pages will then be added and removed as sublayers of the generic view whenever a transition occurs.

You can compile this application, shown in Examples Example 3-22 through Example 3-26, with the SDK by creating a navigation-based application project named FlipDemo. Be sure to pull out the Interface Builder code if you'd like to see how these objects are created from scratch.

Be sure to add the Quartz Core framework to this project before attempting to build it.


Example 3-22. FlipDemo application delegate prototypes (FlipDemoAppDelegate.h)
#import <UIKit/UIKit.h>
#import "RootViewController.h"

@interface FlipDemoAppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    RootViewController *viewController;
    UINavigationController *navigationController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet RootViewController *viewController;
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;

@end

                                          

Example 3-23. FlipDemo application delegate (FlipDemoAppDelegate.m)
#import "FlipDemoAppDelegate.h"
#import "RootViewController.h"


@implementation FlipDemoAppDelegate

@synthesize window;
@synthesize viewController;
@synthesize navigationController;

- (void)applicationDidFinishLaunching:(UIApplication *)application {

    window = [ [ UIWindow alloc ] initWithFrame: [ [ UIScreen mainScreen ] bounds ] ];
    viewController = [ [ RootViewController alloc ] init ];
    navigationController = [ [ UINavigationController alloc ]
        initWithRootViewController: viewController ];

    [ window addSubview: [ navigationController view ] ];
    [ window makeKeyAndVisible ];
}

- (void)dealloc {
    [ navigationController release ];
    [ window release ];
    [ super dealloc ];
}

@end

                                          

Example 3-24. FlipDemo view controller prototype (RootViewController.h)
#import <UIKit/UIKit.h>
#import <UIKit/UITextView.h>

@interface RootViewController : UIViewController {
    UITextView *textView[10];
    UIView *view;
    UIBarButtonItem *prev, *next;
    int page, lastViewed;
}
- (void)setPage;

@end

Example 3-25. FlipDemo view controller (RootViewController.m)
#import <QuartzCore/CAAnimation.h>
#import "RootViewController.h"
#import "FlipDemoAppDelegate.h"

@implementation RootViewController

- (id)init {
    self = [ super init ];
    if (self != nil) {

        /* Default Page */
        page = lastViewed = 5;

        /* Initialize Navigation Buttons */
        prev = [ [ [ UIBarButtonItem alloc ]
                  initWithTitle:@"Prev"
                  style: UIBarButtonItemStylePlain
                  target: self
                  action:@selector(prevpage) ]
                autorelease ];
        self.navigationItem.leftBarButtonItem = prev;

        next = [ [ [ UIBarButtonItem alloc ]
                  initWithTitle:@"Next"
                  style: UIBarButtonItemStylePlain
                  target: self
                  action:@selector(nextpage) ]
                autorelease ];

        self.navigationItem.rightBarButtonItem = next;

    }
    return self;
}

- (void)controlPressed:(id) sender {
    [ self setPage ];
}

- (void)setPage {

    /* Create a new animation */
    CATransition *myTransition = [ CATransition animation ];
    myTransition.timingFunction = UIViewAnimationCurveEaseInOut;
    myTransition.type = kCATransitionPush;
    if (page > lastViewed) {
        myTransition.subtype = kCATransitionFromRight;
    } else {
        myTransition.subtype = kCATransitionFromLeft;
    }

    /* Add the animation to the top view layer */

    [ self.view.layer addAnimation: myTransition forKey: nil ];
    [ self.view insertSubview: textView[page-1] atIndex: 0 ];
    [ textView[lastViewed-1] removeFromSuperview ];

    lastViewed = page;

    if (page == 1)
        prev.enabled = NO;
    else
        prev.enabled = YES;

    if (page == 10)
        next.enabled = NO;
    else
        next.enabled = YES;
}

- (void)prevpage {
    page--;
    [ self setPage ];
}

- (void)nextpage {
    page++;
    [ self setPage ];
}

- (void)loadView {
    CGRect bounds = [ [ UIScreen mainScreen ] applicationFrame ];
    [ super loadView ];

    view = [ [ UIView alloc ] initWithFrame: bounds ];
    bounds.origin.y = 0;

    for(int i = 0;i < 10; i++) {
        textView[i] = [ [ UITextView alloc ] initWithFrame: bounds ];
        textView[i].editable = NO;
        textView[i].text = [ NSString stringWithFormat: @"Page %d", i+1 ];
    }

    self.view = view;
    [ self.view addSubview: textView[4] ];

}

- (void)dealloc {
    for(int i = 0; i < 10; i++) {
        [ textView[i] dealloc ];
    }
    [ next dealloc ];
    [ prev dealloc ];
    [ super dealloc ];
}

@end

                                          

Example 3-26. FlipDemo main (main.m)
#import <UIKit/UIKit.h>

int main(int argc, char *argv[]) {

    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, @"FlipDemoAppDelegate");
    [pool release];
    return retVal;
}

                                          

3.6.4. What's Going On

The FlipDemo example contains everything you've seen so far, and adds an animation to a parent view:

  1. The application instantiates and calls the program's main function, which invokes the FlipDemoAppDelegate class's applicationDidFinishLaunching method.

  2. The application delegate creates an instance of the view controller. The view controller's init method is overridden to define the navigation bar properties to use when displayed, namely, two navigation bar buttons and a segmented control.

  3. The view controller creates ten different UITextView objects, each containing text. A generic UIView object is also created and assigned as the key view for the controller. After the view controller has finished initializing, the application delegate continues by instantiating a navigation controller object, using the view controller as its root controller. The navigation bar controller is then attached to the window as a subview, where it displays itself and the view controller it is wrapping.

  4. When the user taps a button on the navigation bar, the button's action is invoked. The selector adjusts the page number and then notifies the setPage method. This method then creates an animation and attaches it to the controller's key view. Depending on the direction of the page flip, the animation flips either to the left or the right.

  5. The view of the next (or previous) page is added to the key view, and the old page is removed. The animation is automatically executed when this occurs, showing the two pages sliding.

3.6.5. Further Study

Explore transition animations a bit more before moving on:

  • Try creating other types of page transitions using the transition types outlined earlier in this section.

  • Take the UITextView code from previous examples and add two buttons, one for HTML and one for Text. Tapping each button should change the text view to display the file in the corresponding format. Transition from top to bottom and vice-versa when switching between the two views.

  • Check out the following prototypes in your SDK's header files: CAAnimation.h, CAMediaTimingFunction.h, and CALayer.h. You'll find these under /Developer/Platforms/iPhoneOS.platform, inside the QuartzCore framework's Headers directory.