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.
This excerpt is from iPhone Open Application Development. This new edition of iPhone Open Application Development covers the latest version of the open toolkit -- now updated for Apple's iPhone 2.x software and iPhone 3G -- and explains in clear language how to create applications using Objective-C and the iPhone API.
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.
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. Only iPhone firmware versions 1.x are able to
take screenshots from the command line. Versions 2.x and newer can
only take screenshots from within your application, so you’ll need to
integrate this code into your project if you’re running v2.x. 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-darwin9-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. Don’t forget to sign, if necessary, using ldid:
$ 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, “Screen shot example (ScreenDump.m)” 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...\n");
[ self _dumpScreenContents: nil ];
[ self terminate ];
}
int main(int argc, char *argv[])
{
NSAutoreleasePool *autoreleasePool = [
[ NSAutoreleasePool alloc ] init
];
int returnCode = UIApplicationMain(argc, argv, @"ScreenDumpApp", @"ScreenDumpApp");
[ autoreleasePool release ];
return returnCode;
}
@end
Here’s how the screenshot example works:
When the application instantiates, a UIWindow and UIView object are immediately created.
The window’s _setHidden method
is used to keep the window hidden.
The UIApplication class’s
instance method _dumpScreenContents is called, which
dumps the contents of the screen into a file named /tmp/foo_0.png.
The application then calls its own terminate method, effectively killing itself.
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><UINavigationItemView: 0x22fe60></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><UINavigationBar: 0x22f380></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.
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, which is a Cocoa foundation
object to store a URL. 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 ];
As was demonstrated in the last section, the openURL method calls Safari to launch website
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 websites 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.
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, Getting Started with Applications:
$arm-apple-darwin9-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
Chapter 7, Advanced UIKit Design 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 semitransparent 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.
Chapter 5, Advanced Graphics Programming with Core Surface and Quartz Core covered Layer Kit transformations, which allow a layer to be rotated, 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 website, 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-darwin9-gcc -o CovertFlow CovertFlow.m -lobjc \-framework CoreFoundation -framework Foundation -framework UIKit \-framework QuartzCore -framework CoreGraphics \-framework GraphicsServices
Examples A.2 and A.3 contain the code.
Example A.2. QuartzCore 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 <QuartzCore/QuartzCore.h>
#import <QuartzCore/CAScrollLayer.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;
CAScrollLayer *cfIntLayer;
NSMutableArray *pictures;
int selected;
}
+ (CovertFlowApplication *)sharedInstance;
- (void) jumpToCover:(int)index;
- (void) layoutLayer:(CAScrollLayer *)layer;
@end
Example A.3. QuartzCore 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 <QuartzCore/QuartzCore.h>
#import <QuartzCore/CALayer.h>
#import <QuartzCore/CAScrollLayer.h>
#import <QuartzCore/CAAnimation.h>
#import <QuartzCore/CATransition.h>
#import <QuartzCore/CATransaction.h>
#import <QuartzCore/CAMediaTimingFunction.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", @"CovertFlowApplication");
[ autoreleasePool release ];
return returnCode;
}
@implementation CFView
-(id)initWithFrame:(struct CGRect)frame
{
self = [ super initWithFrame: frame ];
if (nil != self) {
[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/mobile/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 = [ [ CAScrollLayer 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:(CAScrollLayer *)layer
{
CALayer *sublayer;
NSArray *array;
size_t i, count;
CGRect rect, cfImageRect;
NSSize cellSize, spacing, margin;
CGSize size;
CATransform 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=CATransform3DMakeRotation(angle,-1,0,0);
rightTransform = CATransform3DMakeRotation(-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 = CATransform3DIdentity;
/* Set perspective */
sublayerTransform.m34 = -0.006;
/* Begin an CATransaction so that all animations happen simultaneously */
[ CATransaction begin ];
[ CATransaction 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 = CGPointMake(0,0);
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 = CATransform3DIdentity;
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 ];
}
[ CATransaction commit ];
}
@end
When the application instantiates, its applicationDidFinishLaunching method is
called by the runtime.
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.
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.
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 CATransform
objects.
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.
A CATransaction 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).
If you enjoyed this excerpt, buy a copy of iPhone Open Application Development.
Copyright © 2009 O'Reilly Media, Inc.