Appendix A. Miscellaneous Hacks and Recipes

Only the major iPhone frameworks have been covered in this book. Dozens of smaller, proprietary frameworks exist on the iPhone waiting to be explored. Through a little bit of hacking, some of these smaller frameworks have proven themselves useful for one-off needs. A number of other interesting recipes have also been concocted for performing tasks such as initiating a phone call or setting the iPhone’s vibrator. This chapter covers some of the hacks and recipes we couldn’t fit anywhere else in the book.

Dumping the Screen

The UIApplication class sports a method named _dumpScreenContents that can be used to make a screenshot on the iPhone. The method causes a file named /tmp/foo_0.png to be written to disk.

To dump the screen contents from within your application, make sure to call the _dumpScreenContents method from your UIApplication class:

[ self _dumpScreenContents: nil ];

You’ll need to have at least a main window with a content view created to take a screenshot. Every call to _dumpScreenContents causes the file to be written to the same location, /tmp/foo_0.png, so be sure to move it out of the way if you’re taking multiple screenshots.

To take screenshots from the command line, you can create an invisible window/view pair to capture whatever else is currently on the screen.

Example: Command-Line Screen Capture Utility

This example takes a snapshot of the screen from the command line, allowing the user to log in via SSH and capture the currently running application. To accomplish this, the example creates an invisible window and view that allows the current application on the screen to show through. When run, the example immediately dumps the screen contents into a file named /tmp/foo_0.png.

To compile this application, use the tool chain on the command line as follows:

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

To use this application, first copy it over to the iPhone using SCP:

$ scp ScreenDump root@iphone:/usr/bin

SSH into the iPhone and run it from the command line:

$ ssh -l root iphone
# /usr/bin/ScreenDump

Example A-1 contains the code.

Example A-1. Screen shot example (ScreenDump.m)
#import <UIKit/UIKit.h>

@interface ScreenDumpApp : UIApplication
{
    UIWindow *window;
}
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
@end

@implementation ScreenDumpApp
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    CGRect rect = [ UIHardware fullScreenApplicationContentRect ];

    window = [ [ UIWindow alloc ] initWithContentRect: rect ];
    [ window orderFront: self ];
    [ window makeKey: self ];
    [ window _setHidden: YES ];
    [ window setContentView: [
        [ UIView alloc ] initWithFrame: rect ]
    ];

    printf("Dumping screen contents...
");
    [ self _dumpScreenContents: nil ];
    [ self terminate ];
}

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

@end

What’s Going On

Here’s how the screenshot example works:

  1. When the application instantiates, a UIWindow and UIView object are immediately created. The window’s _setHidden method is used to keep the window hidden.

  2. The UIApplication class’s instance method _dumpScreenContents is called, which dumps the contents of the screen into a file named /tmp/foo_0.png.

  3. The application then calls its own terminate method, effectively killing itself.

Dumping the UI Hierarchy

In the absence of a fully functional debugger for the iPhone, one aid to developers is the _dumpUIHierarchy method provided by the UIApplication class. A UI dump shows the associations of all display UI objects to each other in a parent/child type of hierarchy. For example, a navigation bar may appear like this in the dump:

<dict>
        <key>CGRect</key>
        <data>
        AAAAAAAAoEEAAKBDAABAQg==
        </data>
        <key>Children</key>
        <array>
                <dict>
                        <key>CGRect</key>
                        <data>
                        AAAAAAAAoEEAAAAAAAAAAA==
                        </data>
                        <key>Enabled</key>
                        <false/>
                        <key>ID</key>
                        <string>&lt;UINavigationItemView: 0x22fe60&gt;</string>
                </dict>
                <dict>
                        <key>CGRect</key>
                        <data>
                        AAB3QwAA+EEAAIhCAADwQQ==
                        </data>
                        <key>Enabled</key>
                        <true/>
                        <key>ID</key>
                        <string>BTN Settings</string>
                </dict>
        </array>
        <key>Enabled</key>
        <true/>
        <key>ID</key>
        <string>&lt;UINavigationBar: 0x22f380&gt;</string>
</dict>

To invoke a dump, call the UIApplication class’s _dumpUIHierarchy instance method:

[ self _dumpUIHierarchy: nil ];

An XML-formatted file will be written to /tmp/UIDump. All windowed objects are written to the dump in the hierarchy in which they are currently allocated.

Invoking Safari

Occasionally, it may be appropriate to call Safari to bring up a web page for your application; for example, when the user presses a “donate” or “home page” button in your application’s credits page. The UIApplication class supports an openURL method that can be used to seamlessly launch Safari and load a web page in a new window.

To use this, your application needs to create an NSURL object. You were introduced to the NSURL in Chapter 6, when recording sound with Celestial, and again in Chapter 7 when exploring web views. The NSURL object is passed to the application’s openURL method, where the application framework processes and launches the appropriate handler application.

NSURL *url;
url = [ [ NSURL alloc ] initWithString: @"http://www.oreilly.com" ];
[ self openURL: url ];

Example: LaunchURL

In this example, the user calls the LaunchURL program from the command line with a URL as a command-line argument:

$ LaunchURL http://www.oreilly.com

To compile this application, use the tool chain on the command line as follows:

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

Example A-2 contains the code.

Example A-2. openURL example (LaunchURL.m)
mport <UIKit/UIKit.h>

@interface LaunchURLApp : UIApplication
{
    NSString *inputURL;
}
- (id)_initWithArgc:(int)argc argv:(const char **)argv;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
@end

@implementation LaunchURLApp
- (id)_initWithArgc:(int)argc argv:(const char **)argv {
    inputURL = [ [ NSString alloc ] initWithCString: argv[1] ];
    return [ super _initWithArgc: argc argv: argv ];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    NSURL *url = [ [ NSURL alloc ] initWithString: inputURL ];
    [ self openURL: url ];
    [ self terminate ];
}

int main(int argc, char *argv[])
{
    NSAutoreleasePool *autoreleasePool = [ [ NSAutoreleasePool alloc ] init ];
    int returnCode;
    if (argc == 2) {
        returnCode = UIApplicationMain(argc, argv, [ LaunchURLApp class ]);
    } else {
        fprintf(stderr, "Syntax: %s [url]
", argv[0]);
    }
    [ autoreleasePool release ];
    return returnCode;
}

@end

What’s Going On

Here’s how the LaunchURL example works:

  1. When the application instantiates, it does a check of the argument count to ensure that it has been called with a URL. If something’s amiss, it prints a syntax line and exits.

  2. The UIApplication class’s _initWithArgc method is overridden and converts the command-line argument into an NSString object. It then calls its superclass’s initialization method.

  3. The application’s applicationDidFinishLaunching method is called when the application object has fully initialized. This creates an NSURL object out of the string and hands it to the openURL method.

  4. Once the openURL method returns, the program self-terminates by calling its own terminate method.

Initiating Phone Calls

As was demonstrated in the last section, the openURL method calls Safari to launch web site URLs. What’s actually going on is this: each protocol is associated with a specific handler application. As was the case with our last demonstration, URLs beginning with http:// and https:// are associated with Safari and cause it to be opened whenever openURL is called using those protocol prefixes. Just as openURL can be used to open web sites in Safari, it can also be used to place phone calls. This is done by using the protocol prefix of tel://:

NSURL *url = [ [ NSURL alloc ]
    initWithString: @"tel://212-555-1234" ];
[ self openURL: url ];

When the openURL method is used on a URL beginning with tel://, the phone application will be launched and the call will be automatically placed. Do try and ensure that your application doesn’t have any bugs and accidentally places expensive overseas calls or prank calls to the White House.

Vibrating

The iPhone includes a built-in vibrating motor for silently notifying the user of new events. This is controlled by the MeCCA framework, which is a private C++ framework used for low-level communications with various devices including audio, Bluetooth, and other hardware. Daniel Peebles has written a low-level vibration example, which can be wrapped into an application or called as a standalone binary.

To vibrate the iPhone, create an instance of the MeCCA_Vibrator C++ class. The prototype for this class looks like this:

class MeCCA_Vibrator {
public:
    int getDurationMinMax(unsigned int&, unsigned int&);
    int activate(unsigned short);
    int activate(unsigned int, unsigned short);
    int activate(unsigned int, unsigned int, unsigned short);
    int deactivate(  );
};

To use this class, first create a new instance of MeCCA_Vibrator:

MeCCA_Vibrator *v = new MeCCA_Vibrator;

The vibrator object’s activate and deactivate methods can then be used to control vibration:

v->activate(1);
usleep(DURATION);
v->deactivate(  );

When calling the usleep( ) function, specify the duration, in microseconds, that you would like the vibrator to run for.

To tap into the MeCCA vibrator object, your application must be linked to the MeCCA framework. Using the tool chain, MeCCA can be linked to your application by adding the -framework MeCCA argument to the compiler arguments we described in Chapter 2:

$ arm-apple-darwin-gcc -o MyApp MyApp.m -lobjc 
    -framework CoreFoundation 
    -framework Foundation 
    -framework MeCCA

To add this option to the sample makefile from the previous chapter, add the MeCCA framework to the linker flags section so that the library is linked in:

LDFLAGS =    -lobjc 
        -framework CoreFoundation 
        -framework Foundation 
        -framework MeCCA

Transparent Views

Chapter 7 introduced the UICompositeImageView class, which allowed multiple images to be layered on top of each other, adding layers of transparency to create overlays. The UIView class provides a similar function called setAlpha, allowing a view’s transparency to be adjusted.

[ mainView setAlpha: 0.5 ];

This can be useful when superimposing multiple views, such as creating semi-transparent buttons and navigation bars, to allow text or images to remain visible through the object. The alpha value ranges from 0 to 1, where 0 makes a view totally invisible and 1 makes the view completely cover whatever is below.

To use this, create two views using overlapping frames. Set the alpha level of the front view using setAlpha. Now, add the first view followed by the second to your controlling view. The frontmost view should be semi-transparent.

Taking Camera Photos

The Photo Library framework is a proprietary framework providing common methods for the iPhone’s Camera application. One useful feature buried inside this framework is the CameraController class, which is capable of snapping 16,001,200 photos in JPEG format.

This is very cool, while also very creepy at the same time. The camera controller itself makes no attempt to notify the user that a photo is being taken, nor is there any indication on the iPhone’s display. This creates the possibility for real spyware (that is, voyeurware) to easily be written and installed on a user’s iPhone without her knowledge.

Installing CameraController Prototypes

The Photo Library framework is not included in the prototype collection for version 0.30 of the tool chain, so you’ll need to dump it directly from the iPhone to begin using the camera controller. The CameraController.h prototype file can be generated using Steve Nygard’s class-dump tool, available at http://www.codethecode.com/projects/class-dump. To dump the contents of the Photo Library framework, use the copy of the iPhone’s libraries you copied over in Chapter 2:

$ class-dump /usr/local/share/iphone-filesystem
/System/Library/Frameworks/PhotoLibrary.framework/PhotoLibrary

This will dump all of the classes in the framework, but you’ll only need one: the CameraController class. Sort through the output of class-dump to find the interface declaration. Place its contents in the file /usr/local/arm-apple-darwin/include/PhotoLibrary/CameraController.h. The contents you’ll be copying into the file will look like this:

@interface CameraController : NSObject
{
    LKLayer *_cameraLayer;
    struct CameraDevice *_camera;
    struct CameraImageQueueHelper *_cameraHelper;
    id _delegate;
    UIView *_previewView;
    BOOL _isPreviewing;
}
+ (id)sharedInstance;
- (id)init;
- (void)_setIsReady;
- (BOOL)isReady;
- (void)_tookPicture:(struct _  _CoreSurfaceBuffer *)fp8;
- (void)_tookPicture:(struct CGImage *)fp8
jpegData:(struct _  _CFData *)fp12 imageProperties:(st
ruct _  _CFDictionary *)fp16;
- (struct CameraImageQueueHelper *)_cameraHelper;
- (BOOL)_setupCamera;
- (void)_tearDownCamera;
- (void)setDelegate:(id)fp8;
- (struct CGRect)_cameraFrame;
- (id)previewView;
- (void)startPreview;
- (void)stopPreview;
- (void)capturePhoto;

@end

Taking Pictures

The CameraController class uses a delegate to notify the application of two kinds of events: ready state and picture taken. You must override two methods to accept these notifications.

The cameraControllerReadyStateChanged method is notified whenever the camera’s state has changed (for example, when it is placed in preview mode). Your own implementation of this method won’t need to perform any special function, but it must exist in your application to avoid generating an exception. This method is useful if you wish to provide some form of notification to the user that the camera is active, such as displaying a red blinking dot.

- (void)cameraControllerReadyStateChanged:
    (NSNotification *)aNotification
{
    /* Take any action here */
}

The tookPicture method is notified whenever a picture is actually taken. The runtime passes the function the raw JPEG data containing the contents of the 1,600 ×1,200 image as well as a UIImage object containing its preview. You’ll need to override this method to access the photo itself.

-(void)cameraController:(id)sender
    tookPicture:(UIImage*)picture
    withPreview:(UIImage*)preview
    jpegData:(NSData*)rawData
    imageProperties:(struct _  _CFDictionary *)imageProperties
{
    /* Save or work with picture here */
}

To take a picture, create an instance of the CameraController class:

    CameraController *camera = [ [ CameraController alloc ] init ];

State and picture notifications should then be directed to a specific object:

[ camera setDelegate: self ];

When you’re ready to snap a picture, first place the camera in preview mode, then call its capturePhoto method:

[ camera startPreview ];
[ camera capturePhoto ];

After a photo has been snapped, the tookPicture method will be notified, allowing you to save the image or work with it in some other way. When you’re finished with the camera, you should turn off the preview mode.

[ camera stopPreview ];

The image data is provided as an NSDATA structure. This is a foundation class present in the Cocoa framework, and is designed to encapsulate raw data into an object. More information about this structure can be found in the Cocoa Reference available on the Apple Developer Connection web site.

Example: Snap App

This example provides a command-line utility for snapping a photo. The program will output “Smile” to stdout and snap a photo one second later. A filename to a JPEG file is provided on the command line.

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

$ arm-apple-darwin-gcc -o snap snap.m -lobjc 
-framework CoreFoundation -framework Foundation -framework UIKit 
-framework PhotoLibrary

Example A-3 contains the code.

Example A-3. Camera controller example (snap.m)
#import <Foundation/Foundation.h>
#import <PhotoLibrary/CameraController.h>
#import <UIKit/UIKit.h>

@interface SnapApp : UIApplication
{
    NSString *filename;
}
- (id)_initWithArgc:(int)argc argv:(const char **)argv;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;
@end

@implementation SnapApp
- (id)_initWithArgc:(int)argc argv:(const char **)argv {
    filename = [ [ NSString alloc ] initWithCString: argv[1] ];
    return [ super _initWithArgc: argc argv: argv ];
}

- (void)cameraControllerReadyStateChanged:(NSNotification *)aNotification
{

}

-(void)cameraController:(id)sender
    tookPicture:(UIImage*)picture
    withPreview:(UIImage*)preview
    jpegData:(NSData*)rawData
    imageProperties:(struct _  _CFDictionary *)imageProperties
{
    [ rawData writeToFile: filename atomically: NO ];
    [ self terminate ];
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    CameraController *camera = [ [ CameraController alloc ] init ];

    [ camera setDelegate: self ];
    [ camera startPreview ];

    printf("Smile...
");
    sleep(1);
    [ camera capturePhoto ];
    [ camera stopPreview ];
}

int main(int argc, char *argv[])
{
    NSAutoreleasePool *autoreleasePool = [
        [ NSAutoreleasePool alloc ] init ];
    int returnCode;
    if (argc == 2) {
        returnCode = UIApplicationMain(argc, argv,
        [ SnapApp class ]);
    } else {
        fprintf(stderr, "Syntax: %s [filename]
", argv[0]);
    }
    [ autoreleasePool release ];
    return returnCode;
}
@end

What’s Going On

  1. When the application instantiates, a CameraController object is created and placed into preview mode to ready the camera. Its delegate is set to self, the application instance running.

  2. This triggers a notification to cameraControllerReadyStateChanged. In our example, this performs no action.

  3. One second later, the controller’s capturePhoto method is called, instructing it to take a snapshot.

  4. The application’s tookPicture method is called, providing the pointer to an NSDATA structure containing the image’s raw JPEG data.

  5. The NSDATA structure’s writeToFile method is called, storing the file on disk.

Cover Flow-Style Album Flipping

Chapter 5 covered Layer Kit transformations, which allow a layer to be rotates, scaled, and transformed in many other ways. Layer Kit is the foundation for Apple’s Cover Flow technology, which is used in selecting albums from the iPod application while in landscape mode.

Layton Duncan of Polar Bear Farm, a software designer for the iPhone, has graciously provided code adapting Apple’s CovertFlow example (from XCode tools’ examples) to the iPhone. The application’s source and images can be downloaded in their entirety at the Polar Bear Farm web site, http://www.polarbearfarm.com.

We’ve spiced up Layton’s example a bit to use the iPhone’s photo album as the album covers. So be sure to snap a few photos with your iPhone before trying this example.

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

$ arm-apple-darwin-gcc -o CovertFlow CovertFlow.m -lobjc 
  -framework CoreFoundation -framework Foundation -framework UIKit 
  -framework LayerKit -framework CoreGraphics 
  -framework GraphicsServices

Example A-4 and Example A-5 contain the code.

Example A-4. LayerKit album example (CovertFlow.h)
#import <Foundation/Foundation.h>
#import <CoreFoundation/CoreFoundation.h>
#import <UIKit/UIKit.h>
#import <UIKit/UIApplication.h>
#import <UIKit/UIScroller.h>
#import <UIKit/UIView-Hierarchy.h>
#import <LayerKit/LayerKit.h>
#import <LayerKit/LKScrollLayer.h>
#import <GraphicsServices/GraphicsServices.h>

/* Number of pixels scrolled before next cover comes front */
#define SCROLL_PIXELS 60.0

/* Size of each cover */
#define COVER_WIDTH_HEIGHT 128.0

@interface CFView : UIScroller
{
    BOOL beating;
}
- (id) initWithFrame:(struct CGRect)frame;
- (void) mouseDragged:(GSEvent*)event;
- (void) heartbeatCallback;
@end

@interface CovertFlowApplication : UIApplication
{
    UIWindow *window;
    CFView *mainView;
    LKScrollLayer *cfIntLayer;
    NSMutableArray *pictures;
    int selected;
}
+ (CovertFlowApplication *)sharedInstance;
- (void) jumpToCover:(int)index;
- (void) layoutLayer:(LKScrollLayer *)layer;
@end
Example A-5. LayerKit album example (CovertFlow.m)
#import <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>
#import <UIKit/CDStructures.h>
#import <UIKit/UIWindow.h>
#import <UIKit/UIView.h>
#import <UIKit/UIView-Hierarchy.h>
#import <UIKit/UIHardware.h>
#import <UIKit/UIResponder.h>
#import <GraphicsServices/GraphicsServices.h>
#import <UIKit/UIView-Geometry.h>
#import <CoreGraphics/CGGeometry.h>
#import <UIKit/UIKit.h>
#import <LayerKit/LayerKit.h>
#import <LayerKit/LKLayer.h>
#import <LayerKit/LKScrollLayer.h>
#import <LayerKit/LKAnimation.h>
#import <LayerKit/LKTransition.h>
#import <LayerKit/LKTransaction.h>
#import <LayerKit/LKTimingFunction.h>
#import "CovertFlow.h"
#import "math.h"

static CovertFlowApplication *sharedInstance;

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

@implementation CFView
-(id)initWithFrame:(struct CGRect)frame
{
    if ((self == [super initWithFrame: frame]) != nil) {
        [self setTapDelegate:self];
        [self setDelegate:self];
        beating = NO;
        [ self startHeartbeat: @selector(heartbeatCallback) inRunLoopMode:nil ];
    }
    return self;
}

- (void) mouseDragged:(GSEvent*)event
{
    if (beating == NO) {

       /* User started flicking through covers.
        * Start a heartbeat to update the coverflow
        */

        beating = YES;
        [ self startHeartbeat: @selector(heartbeatCallback) inRunLoopMode:nil ];
    }
    [ super mouseDragged: event ];
}

- (void)heartbeatCallback
{
    [ [ CovertFlowApplication sharedInstance ]
       jumpToCover:(int) roundf(([ self offset ].y/SCROLL_PIXELS))
    ];

    if (! [self isScrolling] )
    {
        /* Stop the heartbeat when scrolling stops */
        [ self stopHeartbeat: @selector(heartbeatCallback) ];
        beating = NO;
    }
}
@end

@implementation CovertFlowApplication
+ (CovertFlowApplication *)sharedInstance
{
    if (!sharedInstance) {
        sharedInstance = [ [ CovertFlowApplication alloc ] init ];
    }
    return sharedInstance;
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    UIImageView *background;
    CGRect rect;
    NSString *file, *path;
    NSDirectoryEnumerator *dirEnum;
    int i, j;

    /* Read the pictures directory */
    path = [ [ NSString alloc ] initWithString:
       @"/var/root/Media/DCIM/100APPLE" ];
    pictures = [ [ NSMutableArray alloc] init ];
    dirEnum = [ [ NSFileManager defaultManager ] enumeratorAtPath: path ];
    while ((file = [ dirEnum nextObject ])) {
        if ( [ [ file pathExtension ] isEqualToString: @"THM" ])
        {
            [ pictures addObject: [ [ NSString alloc ] initWithString:
                [ path stringByAppendingPathComponent: file ] ] ];
        }
    }
    j = [ pictures count ];

    window = [ [ UIWindow alloc ] initWithContentRect:
        [ UIHardware fullScreenApplicationContentRect ] ];

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

    sharedInstance = self;
    mainView = [ [ CFView alloc ] initWithFrame: rect ];

    /* Set the # of pixels to drag before the scroller moves */
    [ mainView setScrollHysteresis: 64.0 ];

    /* Disable rubber banding */
    [ mainView setAllowsFourWayRubberBanding: NO ];

    /* Cause the scroller to become stiffer */
    [ mainView setScrollDecelerationFactor: 0.9999 ];

    /* Set the scroll view to snap to pixel boundaries */
    [ mainView setGridSize:CGSizeMake(SCROLL_PIXELS, SCROLL_PIXELS) ];

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

    /* Initialize the CovertFlow layer */
    cfIntLayer = [ [ LKScrollLayer alloc ] initWithBounds:
        CGRectMake(0, 0, rect.size.width, rect.size.height + 128)
    ];
    [ cfIntLayer setDelegate:self ];

    /* Position the CovertFlow layer in the middle of the scroll view */
    cfIntLayer.position = CGPointMake(160, 304);
    [ cfIntLayer setDelegate:self ];

    /* Load the album covers */
    for (i=0; i<j; i++) {
        NSString *filename = [ pictures objectAtIndex: i ];

        background = [ [ [ UIImageView alloc ] initWithFrame:
                CGRectMake(0, 0, COVER_WIDTH_HEIGHT, COVER_WIDTH_HEIGHT)
            ]
            autorelease
        ];
        [ background setImage: [ [ UIImage alloc ]
initWithContentsOfFile: filename ] ];
        [ cfIntLayer addSublayer: [ background _layer ] ];
    }

    /* Set the size of the scroll view proportionately to the # covers */
    [ mainView setContentSize:
        CGSizeMake(320, ( (rect.size.height) + (SCROLL_PIXELS*j) ) )
    ];

    /* Add the album layer to the main layer */
    selected = 0;
    [ [ mainView _layer ] addSublayer:cfIntLayer ];
    [ self layoutLayer: cfIntLayer ];
}

- (void) jumpToCover:(int)index
{
    if (index != selected) {
        selected = index;
        [ self layoutLayer:cfIntLayer ];
    }
}

-(void) layoutLayer:(LKScrollLayer *)layer
{
    LKLayer *sublayer;
    NSArray *array;
    size_t i, count;
    CGRect rect, cfImageRect;
    NSSize cellSize, spacing, margin;
    CGSize size;
    LKTransform leftTransform, rightTransform, sublayerTransform;
    float zCenterPosition, zSidePosition;
    float sideSpacingFactor, rowScaleFactor;
    float angle = 1.39;
    int x;

    size = [ layer bounds ].size;

    zCenterPosition = 60;      /* Z-Position of selected cover */
    zSidePosition = 0;         /* Default Z-Position for other covers */
    sideSpacingFactor = .85;   /* How close should slide covers be */
    rowScaleFactor = .55;      /* Distance between main cover and side covers */

    leftTransform = LKTransformMakeRotation(angle, −1, 0, 0);
    rightTransform = LKTransformMakeRotation(-angle, −1, 0, 0);

    margin   = NSMakeSize(5.0, 5.0);
    spacing  = NSMakeSize(5.0, 5.0);
    cellSize = NSMakeSize (COVER_WIDTH_HEIGHT, COVER_WIDTH_HEIGHT);

    margin.width += (size.width - cellSize.width * [ pictures count ]
                  -  spacing.width * ([ pictures count ] - 1)) * .5;
    margin.width = floor (margin.width);

    /* Build an array of covers */
    array = [ layer sublayers ];
    count = [ array count ];

    sublayerTransform = LKTransformIdentity;
    /* Set perspective */
    sublayerTransform.m34 = −0.006;

    /* Begin an LKTransaction so that all animations happen simultaneously */
    [ LKTransaction begin ];
    [ LKTransaction setValue: [ NSNumber numberWithFloat:0.3f ]
        forKey:@"animationDuration" ];

    for (i = 0; i < count; i++)
    {
        sublayer = [ array objectAtIndex:i ];
        x = i;

        rect.size = *(CGSize *)&cellSize;
        rect.origin = CGPointZero;
        cfImageRect = rect;

        /* Base position */
        rect.origin.x = size.width / 2 - cellSize.width / 2;
        rect.origin.y = margin.height + x * (cellSize.height + spacing.height);

        [ [ sublayer superlayer ] setSublayerTransform: sublayerTransform ];

        if (x < selected)        /* Left side */
        {
            rect.origin.y += cellSize.height * sideSpacingFactor
                           * (float) (selected - x - rowScaleFactor);
            sublayer.zPosition = zSidePosition - 2.0 * (selected - x);
            sublayer.transform = leftTransform;
        }
        else if (x > selected)   /* Right side */
        {
            rect.origin.y -= cellSize.height * sideSpacingFactor
                           * (float) (x - selected - rowScaleFactor);
            sublayer.zPosition = zSidePosition - 2.0 * (x - selected);
            sublayer.transform = rightTransform;
        }
        else                     /* Selected cover */
        {
            sublayer.transform = LKTransformIdentity;
            sublayer.zPosition = zCenterPosition;

            /* Position in the middle of the scroll layer */
            [ layer scrollToPoint: CGPointMake(0, rect.origin.y
                - (([ layer bounds ].size.height - cellSize.width)/2.0))
            ];

            /* Position the scroll layer in the center of the view */
            layer.position =
                CGPointMake(160.0f, 240.0f + (selected * SCROLL_PIXELS));
        }
        [ sublayer setFrame: rect ];

    }
    [ LKTransaction commit ];
}

@end

What’s Going On

  1. When the application instantiates, its applicationDidFinishLaunching method is called by the runtime.

  2. This method first walks through the iPhone’s album directory and takes inventory of all thumbnail images. It then initializes a new window and subclass of the UIScroller view, named CFView, into an object named mainView. Various properties of the scroll view are defined here.

  3. Each photo album thumbnail is loaded into a UIImage object and then assigned to a layer. When the application has finished initializing, the layoutLayer method is called.

  4. The layoutLayer method positions every layer in the view to be oriented according to its position. The selected album is brought to the front, while others are rotated using the rightTransform and leftTransform LKTransform objects.

  5. When the user drags his finger, the mouseDragged method is called. This causes a new cover to be selected and causes layoutLayers to be called again.

  6. An LKTransaction is created to ensure that all objects are animated at the same time. The newly selected album is oriented to the front of the screen. When the transaction is committed, the Layer Kit framework performs all of the tweening so that it automatically occurs during the specified time interval for the animation (0.03s).

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

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