Chapter 5. Advanced Graphics Programming with Core Surface and Layer Kit

Core Surface is a C-based framework used for building video buffers in which raw pixel data can be written directly to a screen layer. The framework is used when more advanced rendering is needed than basic graphics files, and is designed for applications that draw their own graphics, created while the program is running. Many different types of pixel formats are supported, and because surfaces have a direct interface to a screen layer, drawing is much faster than using higher-level image classes.

The Layer Kit framework is referred to as Core Animation on the Leopard desktop. Layer Kit provides the underlying classes for managing the content of UIView objects. It is also used to glue a Core Surface buffer to an object on the screen and to create 3-D transformations of 2-D objects for stunning animations and effects.

This chapter will introduce you to both frameworks and illustrate how the two can interoperate to manipulate screen surfaces and deliver different effects.

Understanding Layers

A layer is a low-level object found in many displayable classes. Layers act like a sheet of poster board to which an object’s contents are affixed. It acts as a flexible backing for the object’s display contents and can bend or contort the object in many ways on the screen. Every object that is capable of rendering itself—namely, those derived from the UIView class—has at least one layer to which its contents are glued.

For example, the UIImageView class contains all the basic information about an image—its display region, resolution, and various methods for working with the image. The image itself is glued to a layer, and the layer is used to display the image. The most basic layer behaves like a single clear sheet, and merely presents the image as is. More advanced classes, such as the UICompositeImageView (covered in Chapter 7), contain multiple layers that are treated like transparencies, each with different content, stacked on top of each other to produce one composite image.

Being flexible, layers can be used to manipulate the image. The layer’s class, LKLayer, controls how this “flexible backing” is manipulated as it’s displayed. If you bend the layer, the image will bend with it. If you rotate the layer, the image will come along for the ride. The layer’s transparency (alpha) can be adjusted, an animation can be added, or the object can be rotated, skewed, and scaled as if it were sitting on top of silly putty. The object sitting on top of the layer is completely oblivious to how it is being manipulated, allowing your program to continue seeing the image (or other object) as if it were still a 2-D object. When the user sees the image, however, it’s been contorted to whatever shape the layer has been twisted to.

A layer isn’t limited to just holding image contents. The display output for a UITextView, or any other view class covered in Chapter 3, also sits on top of a layer. This means that navigation bars, tables, text boxes, and many other types of view classes can be transformed, scaled, rotated and even animated.

The important thing to remember about layers is that all UIView objects have one or more, and they control the way in which its contents are ultimately rendered on the screen.

Screen Surfaces

A screen surface is a Core Surface object used to access a raw pixel buffer that is rendered directly to a layer. This allows advanced games or applications to write pixel data directly to a surface on the screen. Core Surface supports many different color value configurations to host different types of pixel data.

Screen surfaces are attached to a layer, which is how the surface can eventually be displayed in a view. The surface is glued to the layer, and the layer is added to a view class, which renders it. This also means that the image rendered on a screen surface can be manipulated using the layer’s methods.

Creating a Screen Surface

A screen surface is an object containing a raw video buffer. It supports a specific resolution, pitch, and pixel format. To create a new screen surface, a buffer is initialized using a dynamic dictionary class named CFMutableDictionary. This is used to provide information about the desired makeup and behavior of the surface. CFMutableDictionary is a class used on both the desktop and mobile platforms of Mac OS X and is part of the Core Foundation framework. Full documentation for this class can be found on the Apple Developer Connection web site.

The following example builds a dictionary object specifying a 320×480 video buffer. It uses RGBA, a four-byte pixel format containing one byte each for red, green, blue, and alpha blending channels. The last method called in the example allocates enough space in the dictionary for all the four-byte pixels in the X×Y rectangle.

CFMutableDictionaryRef dict;
int x = 320, y = 480, pitch = x*4, size = 4*x*y;
char *pixelFormat = "RGBA";

dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
  &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

CFDictionarySetValue(dict, kCoreSurfaceBufferGlobal, kCFBooleanTrue);
CFDictionarySetValue(dict, kCoreSurfaceBufferPitch,
  CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pitch));
CFDictionarySetValue(dict, kCoreSurfaceBufferWidth,
  CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &x));
CFDictionarySetValue(dict, kCoreSurfaceBufferHeight,
  CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &y));
CFDictionarySetValue(dict, kCoreSurfaceBufferPixelFormat,
  CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
  pixelFormat));
CFDictionarySetValue(dict, kCoreSurfaceBufferAllocSize,
  CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &size));

Once a dictionary has been built to define the type of buffer needed, the screen surface can be created using its properties.

CoreSurfaceBufferRef screenSurface = CoreSurfaceBufferCreate(dict);

Displaying the Screen Surface

Before a screen surface can be displayed, it needs to be attached to a layer. Create a new layer using the Layer Kit framework’s LKLayer object.

LKLayer *screenLayer;

screenLayer = [ [ LKLayer layer ] retain ];
[ screenLayer setFrame: viewRect ];
[ screenLayer setOpaque: YES ];

Now, attach the contents of the screen surface to the newly created layer. Note that you always have to lock the screen surface while handling it.

CoreSurfaceBufferLock(screenSurface, 3);
[ screenLayer setContents: screenSurface ];
CoreSurfaceBufferUnlock(screenSurface);

The UIView objects you’ve been working with throughout this book contain their own base layer. To display the surface’s contents, add the newly created layer to the existing UIView as a sublayer.

[ [ self _layer ] addSublayer: screenLayer ];

The screen surface is now glued to the layer, and the layer is added to the view. If there are other layers in the UIView object, the new layer will be placed on top of previous layers, meaning you’ll need to adjust its transparency (alpha) in order to see the layers underneath.

Writing to the Screen Surface

Even after you add the surface’s layer to a view, nothing is displayed yet because the video buffer itself is empty. The CoreSurfaceBuffer object contains a pointer to a raw video buffer. To access the base address of this raw video buffer, use the CoreSurfaceBufferGetBaseAddress function:

unsigned long *baseAddress = CoreSurfaceBufferGetBaseAddress(screenSurface);

The buffer is a single-dimensioned array, even though your application and the user think of it as two-dimensional. Pixels run from left to right and from top to bottom. For instance, baseAddress[0] addresses the upper-left pixel in the surface, baseAddress[319] addresses the upper-right pixel for the 320×480 resolution used in this surface, and baseAddress[320] addresses the leftmost pixel in the second row.

Because an RGBA pixel type is used in this example, each pixel is four bytes long. Using an unsigned long pointer (which is also four bytes long) allows entire pixels to be addressed as elements in an array.

You may also choose to use an unsigned char pointer, which would allow you to address the individual pixel values (one byte for each channel). Keep in mind, however, that you’ll need to increment your pointer four bytes at a time to reach the next pixel (assuming a four-byte pixel type). For example, (unsigned char *) baseAddress[0] references the red channel of the first pixel, baseAddress[1] the green channel, baseAddress[2] the blue channel, baseAddress[3] the alpha channel, and finally baseAddress[4] the red channel of the next pixel.

16-Bit Pixel Formats

16-bit pixel formats are commonly used in lieu of the 32-bit RGBA pixel to deliver faster performance when color accuracy isn’t critical—and because only expensive desktop displays can even support true 32-bit color, there’s not much reason to use it on a cell phone. Because there’s less memory to move around, a 16-bit frame can be copied in half the time as a 32-bit frame. To use this pixel format, make the following changes to the screen surface’s dictionary properties before the surface is created.

int x = 320, y = 480, pitch = x*2, size = 2*x*y;
char *pixelFormat = "565L";

The 16-bit pixel format uses two bytes per pixel, and is identified by the pixel format 565L. Once the surface has been created, it can then be accessed using an unsigned short pointer, which is two bytes long instead of four.

unsigned short *baseAddress = CoreSurfaceBufferGetBaseAddress(screenSurface);

To convert RGB values to 16-bit values suitable for writing to such a buffer, you can define a macro, allowing your application to move smoothly between 16-bit and 32-bit RGBA.

#define RGB2565L(R, G, B) ((R >> 3) << 11) | (( G >> 2) << 5 ) 
    | (( B >> 3 ) << 0 )

Frame Buffer

A frame buffer is a secondary memory buffer used to construct a video frame before it is displayed on a screen surface. This can help prevent flickering by applications that render graphics one scan-line at a time (such as emulators) and can also be used to synchronize programs needing to display in frames per second. Using a frame buffer is also useful for aggregating multiple video layers together before displaying a finished frame. For example, if you are writing a game that draws several different video layers—a background, sprites, and a HUD—then each individual video layer can be drawn onto an internal frame buffer before the finished frame is rendered on the screen. The setup and copying of a frame buffer, if you choose to use one, is the job of the application. When the screen surface is created, its size is calculated as the pixel size multiplied by the width and height of the surface. To use a frame buffer, create a buffer of the same size:

workBuffer = malloc(2 * x * y);

Now, move all of your video processing so that it takes place in this work buffer until the program has finished rendering a frame. When the frame is complete, it is ready to be copied to the surface’s video buffer. The period between the completed frame and the rendering of the next frame is called the v-blank period; this period is when the work buffer can safely be written out to the screen surface’s buffer.

memcpy(baseAddress, workBuffer, 2 * x * y);
[ screenView setNeedsDisplay ];

Example: Random Snow

The first graphics program usually written by any geek is the display of random colors across the resolution of the screen. In keeping with this tradition, our example here creates and displays a screen surface, then applies a coat of rand( ) to each pixel every fifth of a second. When the example is run, the entire screen will be filled with colorful geek snow™.

To build this example, the Core Surface and Layer Kit frameworks must be linked in:

$ arm-apple-darwin-gcc -o MyExample MyExample.m -lobjc 
  -framework CoreFoundation -framework Foundation -framework UIKit 
  -framework CoreSurface -framework LayerKit

Example 5-1 and Example 5-2 contain the code for the application.

Example 5-1. Core Surface example (MyExample.h)
#import <CoreFoundation/CoreFoundation.h>
#import <UIKit/UIKit.h>
#import <CoreSurface/CoreSurface.h>

@interface MainView : UIView
{
    CoreSurfaceBufferRef screenSurface;
    unsigned short *baseAddress;
    LKLayer *screenLayer;
}

- (id)initWithFrame:(CGRect)frame;
- (void)drawRect:(CGRect)rect;
- (CoreSurfaceBufferRef)screenSurface;
- (void)dealloc;

@end

@interface MyApp : UIApplication
{
    UIWindow *window;
    MainView *mainView;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
@end
Example 5-2. Core Surface example (MyExample.m)
#import "MyExample.h"

int main(int argc, char **argv)
{
    NSAutoreleasePool *autoreleasePool = [ [ NSAutoreleasePool alloc ] init ];
    int returnCode = UIApplicationMain(argc, argv, [ MyApp class ]);
    [ autoreleasePool release ];
    return returnCode;
}

@implementation MyApp
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    window = [ [ UIWindow alloc ] initWithContentRect:
        [ UIHardware fullScreenApplicationContentRect ]
    ];

    CGRect rect = [ UIHardware fullScreenApplicationContentRect ];
    rect.origin.x = rect.origin.y = 0.0f;

    mainView = [ [ MainView alloc ] initWithFrame: rect ];

    [ window setContentView: mainView ];
    [ window orderFront: self ];
    [ window makeKey: self ];
    [ window _setHidden: NO ];

    NSTimer *timer = [ NSTimer scheduledTimerWithTimeInterval: 0.05
            target: self
            selector: @selector(handleTimer:)
            userInfo: nil
            repeats: YES ];
}

- (void) handleTimer: (NSTimer *) timer
{
    CoreSurfaceBufferRef screenSurface;
    unsigned short *baseAddress;
    int i;
    screenSurface = [ mainView screenSurface ];
    baseAddress = CoreSurfaceBufferGetBaseAddress(screenSurface);

    for(i=0;i < 320 * 480;i++)
        baseAddress[i] = rand(  ) % 0xFFFF;
    [ mainView setNeedsDisplay ];
}
@end

@implementation MainView
- (id)initWithFrame:(CGRect)rect {

    if ((self == [ super initWithFrame: rect ]) != nil) {
        CFMutableDictionaryRef dict;
        int x = 320, y = 480, pitch = x * 2, size = 2 * x * y, i;
        char *pixelFormat = "565L";

        /* Create a screen surface */
        dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
            &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        CFDictionarySetValue(dict, kCoreSurfaceBufferGlobal, kCFBooleanTrue);
        CFDictionarySetValue(dict, kCoreSurfaceBufferPitch,
            CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &pitch));
        CFDictionarySetValue(dict, kCoreSurfaceBufferWidth,
            CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &x));
        CFDictionarySetValue(dict, kCoreSurfaceBufferHeight,
            CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &y));
        CFDictionarySetValue(dict, kCoreSurfaceBufferPixelFormat,
            CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type,
            pixelFormat));
        CFDictionarySetValue(dict, kCoreSurfaceBufferAllocSize,
            CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &size));

        screenSurface = CoreSurfaceBufferCreate(dict);

        screenLayer = [ [ LKLayer layer ] retain ];
        [ screenLayer setFrame: rect ];
        [ screenLayer setContents: screenSurface ];
        [ screenLayer setOpaque: YES ];

        CoreSurfaceBufferLock(screenSurface, 3);

        [ [ self _layer ] addSublayer: screenLayer ];

        CoreSurfaceBufferUnlock(screenSurface);
    }

    return self;
}

- (void)drawRect:(CGRect)rect {

}

- (CoreSurfaceBufferRef)screenSurface {
    return screenSurface;
}

- (void)dealloc
{
    [ screenLayer release ];
    [ self dealloc ];
    [ super dealloc ];
}

@end

What’s Going On

The geek snow example works like this:

  1. When the application instantiates, a MainView object is created and its initWithFrame method is called.

  2. initWithFrame creates a 16-bit 320×480 screen surface and glues it to its own LKLayer object. It then adds this layer to the main view.

  3. An NSTimer object is created, causing a method named handleTimer to be called every 0.05 seconds. This steps through the entire surface and applies a random color value to each pixel. It also calls the object’s setNeedsDisplay method, which instructs the object to repaint the screen.

Layer Animation

While Layer Kit proved useful in the previous section to glue a Core Surface buffer to the user interface, its capabilities extend far beyond a mere sticky layer. Layer Kit can be used to transform a 2-D object into a stunning 3-D texture that can be used to create beautiful transitions between views.

Chapter 3 introduced the UITransitionView class as a means of transitioning between different UIView objects. This class offered some basic animation, but was largely two-dimensional. The Layer Kit framework provides a more advanced set of tools for performing layer animation, allowing more spectacular transitions.

Creating a Layer Transition

Layer transitions augment the existing UITransitionView class by providing a way to override its transitions with new ones using Layer Kit’s animation engine. This allows the developer to take advantage of the more advanced 3-D capabilities offered by Layer Kit without making significant changes to their code. When a layer transition, represented by an LKTransition object, is attached to a UITransitionView, the transition invokes Layer Kit to spawn a new thread that takes over all of the graphics processing for the transition. The developer needs only to add the desired transition to enhance an existing application. One such animation is added by the following code:

    LKAnimation *animation = [ LKTransition animation ];
    [ animation setType: @"pageCurl" ];
    [ animation setSubtype: @"fromRight" ];
    [ animation setTimingFunction:
        [ LKTimingFunction functionWithName: @"easeInEaseOut" ] ];
    [ animation setFillMode: @"extended" ];
    [ animation setTransitionFlags: 3 ];
    [ animation setSpeed: 0.25 ];

Available animations

The previous example specifies the pageCurl animation using the setType method. The following animation types are available from Layer Kit.

Type

Description

pageCurl

The previous view curls off as if being peeled from a note pad, revealing the new view underneath.

pageUnCurl

The new view is curled over the old; reverse of pageCurl.

suckEffect

The old view is sucked through the bottom center of the window, revealing the new page underneath.

spewEffect

The new view is regurgitated up through the bottom center of the window; reverse of suckEffect.

genieEffect

The old view is sucked through the bottom left or right of the window, revealing the new page underneath.

unGenieEffect

The new view is regurgitated up through the bottom left or right of the window; reverse of genieEffect.

twist

The view is turned horizontally in a twisted cyclone fashion.

tubey

The view is turned vertically in an elastic fashion.

swirl

The old view fades to the new view, while the window itself swirls 360 degrees.

rippleEffect

The new view ripples into the window. This does not appear to work properly with full screen transitions.

cameraIris

A camera shutter closes on the old view, and opens to reveal the new view.

cameraIrisHollow

Same as cameraIris, only the old view is removed before the shudder is closed.

cameraIrisHollowOpen

A camera shutter opens into the new view only; animation begins with the shutter closed.

cameraIrisHollowClose

A camera shutter closes onto the old view; animation does not reopen the shutter.

charminUltra

The old view gently and comfortably fades into the new view, making you feel clean and refreshed.

zoomyIn

The new view zooms in from the back; the old view zooms out the front and vanishes.

zoomyOut

The new view zooms in from the front; the old view zooms out the back.

oglApplicationSuspend

The old view zooms out the back; the new view displays immediately. Resembles pressing the home button in an application.

oglFlip

The view flips horizontally, revealing the new page.

Available subtypes

The following subtypes can be used to define the direction in which the animation flows. These are set using the setSubtype method.

Type

Description

fromLeft

The animation flows from left to right.

fromRight

The animation flows from right to left.

fromTop

The animation flows from top to bottom.

fromBottom

The animation flows from bottom to top.

Animation speed and timing

The animation speed has a very small value range, from 0.0 (not moving) to about 5.0 (instantaneous). The fastest desirable speed will likely be 1.0.

The timing function defines the balance between the transition-out part of the animation and the transition-in part of the animation. The following different timings can be used.

Type

Description

easeInEaseOut

Both halves of the animation receive equal time.

easeIn

The second half of the animation is performed faster.

easeOut

The first half of the animation is performed faster.

Transition flags

The transition flags determine how edges are anti-aliased. The value is an OR’d set of values based on the following macros.

Macro

Meaning

Bit position

kLKLayerLeftEdge

Anti-alias left edges

1 << 0 (bit 0)

kLKLayerRightEdge

Anti-alias right edges

1 << 1

kLKLayerBottomEdge

Anti-alias bottom edges

1 << 2

kLKLayerTopEdge

Anti-alias top edges

1 << 3

Displaying the Layer Transition

Once a Layer Kit animation has been configured, it is executed through a UITransitionView object. Before the transition view’s transition method is called, the animation must first attach to the view that will be animated. To animate all of the contents on the screen, the animation can be applied to the main view. This will cause all objects belonging to the view, such as navigation bars and buttons, to be included in the animation. If you’re working with a smaller object, such as a UITextView or UIPushButton object, apply the animation to the transition view itself. This will cause only the smaller object to be affected.

[ [ transitionView _layer ] addAnimation: animation forKey: 0 ];

This line adds the animation to the layer so that when it is transitioned, the Layer Kit animation will be run in place of transition #0. Now, the only thing left is to call the transition:

[ transitionView transition: 0 toView: newView ];

Example: Page Flipping with Style

In Chapter 3, you built a page-flipping program to illustrate transitions. We’ll use the same example here, but add Layer Kit animations to flip between pages.

This example can be compiled using the tool chain on the command line:

$ arm-apple-darwin-gcc -o MyExample MyExample.m -lobjc 
    -framework UIKit -framework CoreSurface 
    -framework CoreFoundation -framework LayerKit 
    -framework Foundation

Example 5-3 and Example 5-4 contain the code for the new version of the page-flipping program.

Example 5-3. LayerKit animation example (MyExample.h)
#import <CoreFoundation/CoreFoundation.h>
#import <UIKit/UIKit.h>
#import <UIKit/UINavigationBar.h>
#import <UIKit/UINavigationItem.h>
#import <UIKit/UITransitionView.h>
#import <UIKit/UITextView.h>
#import <LayerKit/LKTransition.h>
#import <LayerKit/LKAnimation.h>

#define MAX_PAGES 10

@interface MainView : UIView
{
    UINavigationBar    *navBar;    /* Our navigation bar */
    UINavigationItem   *navItem;   /* Navigation bar title */
    UITransitionView   *transView; /* Our transition */
    int                pageNum;    /* Current page number */

    /* Some pages to scroll through */
    UITextView         *textPage[MAX_PAGES];
}

- (id)initWithFrame:(CGRect)frame;
- (void)dealloc;
- (UINavigationBar *)createNavBar:(CGRect)rect;
- (void)setNavBar;
- (void)navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button;
- (void)flipTo:(int)page;

@end

@interface MyApp : UIApplication
{
    UIWindow *window;
    MainView *mainView;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
@end
Example 5-4. LayerKit animation example (MyExample.m)
#import "MyExample.h"

int main(int argc, char **argv)
{
    NSAutoreleasePool *autoreleasePool = [
        [ NSAutoreleasePool alloc ] init
    ];
    int returnCode = UIApplicationMain(argc, argv, [ MyApp class ]);
    [ autoreleasePool release ];
    return returnCode;
}

@implementation MyApp

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    window = [ [ UIWindow alloc ] initWithContentRect:
        [ UIHardware fullScreenApplicationContentRect ]
    ];

    CGRect rect = [ UIHardware fullScreenApplicationContentRect ];
    rect.origin.x = rect.origin.y = 0.0f;

    mainView = [ [ MainView alloc ] initWithFrame: rect ];

    [ window setContentView: mainView ];
    [ window orderFront: self ];
    [ window makeKey: self ];
    [ window _setHidden: NO ];
}
@end

@implementation MainView
- (id)initWithFrame:(CGRect)rect {
    if ((self == [ super initWithFrame: rect ]) != nil) {
        CGRect viewRect;
        int i;

        /* Create a new view port below the navigation bar */
        viewRect = CGRectMake(rect.origin.x, rect.origin.y + 48.0,
            rect.size.width, rect.size.height - 48.0);

        /* Set our start page */
        pageNum = MAX_PAGES / 2;

        /* Create ten UITextView objects as pages in our book */

        for(i=0;i<MAX_PAGES;i++) {
            textPage[i] = [ [ UITextView alloc ] initWithFrame: rect ];
            [ textPage[i] setText: [ [ NSString alloc ] initWithFormat:
                @"This is some text for page %d", i+1 ] ];
        }

        /* Creat a navigation bar with 'Prev' and 'Next' buttons */
        navBar = [ self createNavBar: rect ];
        [ self setNavBar ];
        [ self addSubview: navBar ];

        /* Create our transition view */
        transView = [ [ UITransitionView alloc ] initWithFrame: viewRect ];
        [ self addSubview: transView ];

        /* Transition to the first page */
        [ self flipTo: pageNum ];
    }

    return self;
}

- (void)dealloc
{
    [ navBar release ];
    [ navItem release ];
    [ self dealloc ];
    [ super dealloc ];
}

- (UINavigationBar *)createNavBar:(CGRect)rect {
    UINavigationBar *newNav = [ [UINavigationBar alloc] initWithFrame:
        CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, 48.0f)
    ];

    [ newNav setDelegate: self ];
    [ newNav enableAnimation ];

    /* Add our title */
    navItem = [ [UINavigationItem alloc] initWithTitle:@"My Example" ];
    [ newNav pushNavigationItem: navItem ];

    [ newNav showLeftButton:@"Prev"  withStyle: 0
                rightButton:@"Next" withStyle: 0 ];

    return newNav;
}
- (void)setNavBar
{

    /* Enable or disable our page buttons */

    if (pageNum == 1)
        [ navBar setButton: 1 enabled: NO ];
    else
        [ navBar setButton: 1 enabled: YES ];

    if (pageNum == MAX_PAGES)
        [ navBar setButton: 0 enabled: NO ];
    else
        [ navBar setButton: 0 enabled: YES ];
}

- (void)navigationBar:(UINavigationBar *)navbar buttonClicked:(int)button
{
    /* Next Page */
    if (button == 0)
    {
        [ self flipTo: pageNum+1 ];
    }

    /* Prev Page */
    else {
        [ self flipTo: pageNum−1 ];
    }

}

- (void)flipTo:(int)page {

    LKAnimation *animation = [ LKTransition animation ];
    [ animation setType: @"oglFlip" ];
    [ animation setSubtype: @"fromLeft" ];
    [ animation setTimingFunction:
        [ LKTimingFunction functionWithName: @"easeOut" ] ];
    [ animation setFillMode: @"extended" ];
    [ animation setTransitionFlags: 3 ];
    [ animation setSpeed: 0.50 ];
    [ [ self  _layer ] addAnimation: animation forKey: 0 ];

    [ transView transition: 0 toView: textPage[page−1] ];
    pageNum = page;
    [ self setNavBar ];
}

@end

What’s Going On

This example works in an almost identical fashion to Example 3-9 and Example 3-10 in Chapter 3.

  1. When the application initializes, a MainView object is created and its initWithFrame method is called. This creates 10 UITextView objects to serve as our reading page examples. It then creates the navigation bar and one transition view. Finally, a method called flipTo is called, which is responsible for flipping to the page number specified.

  2. The flipTo member function builds an LKAnimation object and adds it to the main view’s layer. It then calls the transition view to do the work of triggering the animation while simultaneously moving to the desired page. The main view layer’s animation is automatically used, which causes the contents of the entire main view to transition, instead of just the page being flipped.

  3. When the user presses the Prev or Next navigation buttons, the buttonClicked method gets called with a pointer to the navigation bar and the button number. From here, the flipTo method is called again to transition to the new page and disable either navigation button if it has reached one end of the book.

Further Study

Check out the LKLayer.h, LKAnimation.h, and LKTransition.h prototypes in your tool chain’s include directory. You’ll find it in /usr/local/arm-apple-darwin/include/LayerKit/.

Layer Transformations

Layer Kit’s rendering capabilities allow a 2-D image to be freely manipulated as if it were 3-D. An image can be rotated on an X-Y-Z axis to any angle, scaled, and skewed. The LKTransform object is the magic behind Apple’s Cover Flow® technology. The Apple desktop uses the Core Animation framework to process 3-D transformations. The iPhone’s Layer Kit framework uses many of the same functions, but has renamed them with the prefix LK (Layer Kit) instead of CA (Core Animation). The iPhone supports scale, rotation, affine, and translation transformations. More information about these various transformations can be found in Apple’s Core Animation Programming Guide available on the Apple Developer Connection web site.

A transformation is carried out on the level of individual layers. The Layer Kit framework performs transformations using an LKTransform object. This object is applied to a view’s layer to bend or otherwise manipulate its layer into the desired 3-D configuration. The application continues to treat the object as if it’s a 2-D object, but when it is presented to the user, the object conforms to whatever transformation has been applied to the layer. The following example creates a transformation for the purpose of rotating a layer.

LKTransform myTransform;
myTransform = LKTransformMakeRotation(angle, x, y, z);

The LKTransformMakeRotation method creates a transformation that will rotate a layer by angle radians using an axis of X-Y-Z. The values for X-Y-Z define the axis and magnitude for each space (between −1 and +1). Assigning a value to an axis instructs the transformation to rotate using that axis. For example, if the X-axis is set to either −1 or +1, the object will be rotated on the X-axis in that direction, meaning it will be rotated vertically. Think of these values as inserting straws through the image for each axis. If a straw were inserted across the X-axis, the image would spin around the straw vertically. More complex rotations can be created using axis angle values. For most uses, however, values of −1 and +1 are sufficient.

To rotate a layer by 45 degrees on its horizontal axis (rotating vertically), the following might be used:

myTransform = LKTransformMakeRotation(0.78, 1.0, 0.0, 0.0);

To rotate the same amount horizontally, specify a value for the Y-axis instead:

myTransform = LKTransformMakeRotation(0.78, 0.0, 1.0, 0.0);

The value 0.78 used above represents the radian value of the angle. To calculate radians from degrees, use the simple formula Mπ/180. For example, 45π/180 = 45(3.1415)/180 = 0.7853.

Once the transformation has been created, it’s then applied to the layer being operated on. To access the layer, use the _layer method inside of any view object to return its LKLayer object. The LKLayer object has a setTransform method that you use to attach the transformation to it. This instructs the layer to perform the transformation as specified.

[ [ imageView _layer ] setTransform: myTransform ];

Example: Spinning Wallpaper Demo

This example makes use of Layer Kit’s LKTransformMakeRotate transformation to spin the desktop wallpaper many different ways on the X, Y, and Z axes. The wallpaper is loaded into a UIAutocorrectImageView class, which is used to scale the image down to half its size (more specifics will be covered in Chapter 7). A timer is then used to adjust the image’s rotation every 0.01 seconds, changing the axis at the end of every 360-degree spin.

To compile this example, use the tool chain on the command line:

$ arm-apple-darwin-gcc -o MyExample MyExample.m -lobjc 
-framework Foundation -framework CoreFoundation -framework UIKit 
-framework LayerKit

Example 5-5 and Example 5-6 show the code.

Example 5-5. Layer transformation example (MyExample.h)
#import <CoreFoundation/CoreFoundation.h>
#import <UIKit/UIKit.h>
#import <LayerKit/LayerKit.h>
#import <LayerKit/LKTransform.h>

@interface MainView : UIView
{
    UIAutocorrectImageView *imageView;
    LKTransform transform;
    NSTimer *timer;

    float angle, x, y, z;
}

- (id)initWithFrame:(CGRect)frame;
- (void) handleTimer: (NSTimer *) timer;
- (void)dealloc;

@end

@interface MyApp : UIApplication
{
    UIWindow *window;
    MainView *mainView;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
@end
Example 5-6. Layer transformation example (MyExample.m)
#import <UIKit/UIKit.h>
#import <UIKit/UIAutocorrectImageView.h>
#import "MyExample.h"

int main(int argc, char **argv)
{
    NSAutoreleasePool *autoreleasePool = [
        [ NSAutoreleasePool alloc ] init
    ];
    int returnCode = UIApplicationMain(argc, argv, [ MyApp class ]);
    [ autoreleasePool release ];
    return returnCode;

}

@implementation MyApp

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    window = [ [ UIWindow alloc ] initWithContentRect:
        [ UIHardware fullScreenApplicationContentRect ]
    ];

    CGRect rect = [ UIHardware fullScreenApplicationContentRect ];
    rect.origin.x = rect.origin.y = 0.0f;

    mainView = [ [ MainView alloc ] initWithFrame: rect ];

    [ window setContentView: mainView ];
    [ window orderFront: self ];
    [ window makeKey: self ];
    [ window _setHidden: NO ];
}

@end

@implementation MainView
- (id)initWithFrame:(CGRect)rect {

    if ((self == [ super initWithFrame: rect ]) != nil) {
        UIImage *tempImage;

        angle = y = z = 0;
        x = 1;

        tempImage = [ UIImage defaultDesktopImage ];
        imageView =  [ [ UIAutocorrectImageView alloc ]
            initWithFrame: CGRectMake(80, 120, 160, 240)
            image: tempImage ];
        [ [ self _layer ] addSublayer: [ imageView _layer ] ];

        transform = LKTransformMakeRotation(angle, x, y, z);
        [ imageView _layer ].transform = transform;

        [ self setNeedsDisplay ];

        timer = [ NSTimer scheduledTimerWithTimeInterval: 0.01
            target: self
            selector: @selector(handleTimer:)
            userInfo: nil
            repeats: YES ];
    }

    return self;
}

- (void) handleTimer: (NSTimer *) timer
{
    angle += 0.01;
    if (angle > 6.283) {
        angle = 0;
        if (z == 1) {
            x = 0;
        }
        else {
            if (y == 1) {
                z = 1;
            }
            y = 1;
       }
    }

    [ [ imageView _layer] setTransform:
        LKTransformRotate(transform, angle, x, y, z)
    ];
}

- (void)dealloc
{
    [ imageView release ];
    [ self dealloc ];
    [ super dealloc ];
}

@end

What’s Going On

The transformation demo works like this:

  1. When the application instantiates, a controlling view named mainView is created and its initWithFrame method is called.

  2. This initializes the angle and X-Y-Z axes, and loads the desktop wallpaper into a UIAutocorrectImageView where it is scaled down to half its size. The image is displayed in the middle of the screen.

  3. A timer is started to call the handleTimer method every 0.01 seconds. With each timer heartbeat, the angle is increased and wraps around after reaching 360 degrees (6.283 radians).

  4. The transformation is modified using the LKTransformRotate method and applied to the layer. This gives the appearance of the image rotating in the middle of the screen.

  5. Every time the angle wraps, different axes are set, effectively changing the direction in which the object is rotating.

Further Study

Explore transformations a little more and try these exercises:

  1. Check out the LKTransform.h and LKLayer.h prototypes in your tool chain’s include directory. These can be found in /usr/local/arm-apple-darwin/include/LayerKit/.

  2. Check out http://en.wikipedia.org/wiki/Axis_angle, which explains how axis angles work. This might come in handy at designing custom rotation transformations.

  3. Check out Apple’s Core Animation guide on the Apple Developer Connection web site. The Core Animation functions mirror the iPhone’s LayerKit functions. It can be found at: http://developer.apple.com/documentation/Cocoa/Conceptual/CoreAnimation_guide/.

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

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