iPhone OS Reference Library Apple Developer Connection spyglass button

Device Support

iPhone OS supports a variety of features that make the mobile computing experience compelling for users. Through iPhone OS, your applications can access hardware features, such as the accelerometers and camera, and software features, such as the user’s photo library. The following sections describe these features and show you how to integrate them into your own applications.

Setting Required Hardware Capabilities

If your application requires device-related features in order to run, you can add a list of required capabilities to your application. At runtime, iPhone OS will not launch your application unless those capabilities are present on the device. Further, the App Store will also restrict your application to users whose devices are capable of running your application.

You add the list of required capabilities by adding the UIRequiredDeviceCapabilities key to your application’s info.plist file. This key is supported in iPhone OS 3.0 and later. The value of this key is either an array or a dictionary. If you use an array, the presence of a given key indicates the corresponding feature is required. If you use a dictionary, you must specify a Boolean value for each key indicating whether the feature is required. In both cases, not including a key indicates that the feature is not required.

Table 8-1 lists the keys that you can include in the array or dictionary associated with the UIRequiredDeviceCapabilities key. You should include keys only for the features that your application absolutely requires. If your application can accommodate missing features by not executing the appropriate code paths, you do not need to include the corresponding key.

Table 8-1  Dictionary keys for the UIRequiredDeviceCapabilities key

Key

Description

telephony

Include this key if your application requires the presence of the Phone application. You might require this feature if your application opens URLs with the tel scheme.

sms

Include this key if your application requires the presence of the Messages application. You might require this feature if your application opens URLs with the sms scheme.

still-camera

Include this key if your application uses the UIImagePickerController interface to capture images from the device’s still camera.

auto-focus-camera

Include this key if your application requires auto-focus capabilities in the device’s still camera. Although most developers should not need to include this key, you might include it if your application supports macro photography or requires sharper images in order to do some sort of image processing.

video-camera

Include this key if your application uses the UIImagePickerController interface to capture video from the device’s camera.

wifi

Include this key if your application requires access to the networking features of the device.

accelerometer

Include this key if your application uses the UIAccelerometer interface to receive accelerometer events. You do not need to include this key if your application detects only device orientation changes.

location-services

Include this key if your application uses the Core Location framework to access the device’s current location. (This key refers to the general location services feature. If you specifically need GPS-level accuracy, you should also include the gps key.)

gps

Include this key if your application requires the presence of GPS (or AGPS) hardware for greater accuracy when tracking locations. If you include this key, you should also include the location-services key. You should require GPS only if your application needs more accurate location data than the cell or Wi-fi radios might otherwise allow.

magnetometer

Include this key if your application uses Core Location framework to receive heading-related events.

microphone

Include this key if your application uses the built-in microphone or supports accessories that provide a microphone.

opengles-1

Include this key if your application requires the presence of the OpenGL ES 1.1 interfaces.

opengles-2

Include this key if your application requires the presence of the OpenGL ES 2.0 interfaces.

armv6

Include this key if your application is compiled only for the armv6 instruction set. (iPhone OS v3.1 and later.)

armv7

Include this key if your application is compiled only for the armv7 instruction set. (iPhone OS v3.1 and later.)

peer-peer

Include this key if your application requires peer-to-peer connectivity over Bluetooth. (iPhone OS v3.1 and later.)

Determining the Available Hardware Support

Applications designed for iPhone OS must be able to run on devices with different hardware features. Although features such as the accelerometers and Wi-Fi networking are available on all devices, some devices may not include a camera or GPS hardware. If your application requires such features, you should always notify the user of that fact before they purchase the application. For features that are not required, but which you might want to support when present, you must check to see if the feature is available before trying to use it.

Important: If a feature absolutely must be present in order for your application to run, configure the UIRequiredDeviceCapabilities key in your application’s Info.plist file accordingly. This key prevents users from installing applications that require features that are not present on a device. You should not include a key, however, if your application can function with or without the given feature. For more information about configuring this key, see “The Information Property List.”

Table 8-2 lists the techniques for determining if certain types of hardware are available. You should use these techniques in cases where your application can function without the feature but still uses it when present.

Table 8-2  Identifying available hardware features

Feature

Options

To determine if the network is available…

Use the reachability interfaces of the Software Configuration framework to determine the current network connectivity. For an example of how to use the Software Configuration framework, see Reachability.

To determine if the still camera is available…

Use the isSourceTypeAvailable: method of the UIImagePickerController class to determine if the camera is available. For more information, see “Taking Pictures with the Camera.”

To determine if audio input (a microphone) is available…

In iPhone OS 3.0 and later, use the AVAudioSession class to determine if audio input is available. This class accounts for many different sources of audio input on iPhone OS–based devices, including built-in microphones, headset jacks, and connected accessories. For more information, see AVAudioSession Class Reference.

To determine if GPS hardware is present…

Specify a high accuracy level when configuring the CLLocationManager object to deliver location updates to your application. The Core Location framework does not provide direct information about the availability of specific hardware but instead uses accuracy values to provide you with the data you need. If the accuracy reported in subsequent location events is insufficient, you can let the user know. For more information, see “Getting the User’s Current Location”

To determine if a specific accessory is available…

Use the classes of the External Accessory framework to find the appropriate accessory object and connect to it. For more information, see “Communicating with External Accessories.”

Communicating with External Accessories

In iPhone OS 3.0 and later, the External Accessory framework (ExternalAccessory.framework) provides a conduit for communicating with accessories attached to an iPhone or iPod touch device. Application developers can use this conduit to integrate accessory-level features into their applications.

Note: The following sections show you how to connect to accessories from an iPhone application. If you are interested in becoming a developer of accessories for iPhone or iPod touch, you can find information about how to do so on http://developer.apple.com.

To use the features of the External Accessory framework, you must add ExternalAccessory.framework to your Xcode project and link against it in any relevant targets. To access the classes and headers of the framework, include an #import <ExternalAccessory/ExternalAccessory.h> statement at the top of any relevant source files. For more information on how to add frameworks to your project, see Files in Projects in Xcode Project Management Guide. For general information about the classes of the External Accessory framework, see External Accessory Framework Reference.

Accessory Basics

Communicating with an external accessory requires you to work closely with the accessory manufacturer to understand the services provided by that accessory. Manufacturers must build explicit support into their accessory hardware for communicating with iPhone OS. As part of this support, an accessory must support at least one command protocol, which is a custom scheme for sending data back and forth between the accessory and an attached application. Apple does not maintain a registry of protocols; it is up to the manufacturer to decide which protocols to support and whether to use custom protocols or standard protocols supported by other manufacturers.

As part of your communication with the accessory manufacturer, you must find out what protocols a given accessory supports. To prevent namespace conflicts, protocol names are specified as reverse-DNS strings of the form com.apple.myProtocol. This allows each manufacturer to define as many protocols as needed to support their line of accessories.

Applications communicate with an accessory by opening a session to that accessory using a specific protocol. You open a session by creating an instance of the EASession class, which contains NSInputStream and NSOutputStream objects for communicating with the accessory. Your application uses these stream objects to send raw data packets to the accessory and to receive similar packets in return. Therefore, you must understand the expected format of each data packet based on the protocol you want to support.

Declaring the Protocols Your Application Supports

Applications that are able to communicate with an external accessory should declare the protocols they support in their Info.plist file. Declaring support for specific protocols lets the system know that your application can be launched when that accessory is connected. If no application supports the connected accessory, the system may choose to launch the App Store and point out applications that do.

To declare the protocols your application supports, you must include the UISupportedExternalAccessoryProtocols key in your application’s Info.plist file. This key contains an array of strings that identify the communications protocols that your application supports. Your application can include any number of protocols in this list and the protocols can be in any order. The system does not use this list to determine which protocol your application should choose; it uses it only to determine if your application is capable of communicating with the accessory. It is up to your code to choose an appropriate communications protocol when it begins talking to the accessory.

Connecting to an Accessory at Runtime

Accessory are not visible through the External Accessory framework until they have been connected by the system and made ready for use. When an accessory does become visible, your application can get the appropriate accessory object and open a session using one or more of the protocols supported by the accessory.

The shared EAAccessoryManager object provides the main entry point for applications looking to communicate with accessories. This class contains an array of already connected accessory objects that you can enumerate to see if there is one your application supports. Most of the information in an EAAccessory object (such as the name, manufacturer, and model information) is intended for display purposes only. To determine whether your application can connect to an accessory, you must look at the accessory’s protocols and see if there is one your application supports.

Note: It is possible for more than one accessory object to support the same protocol. If that happens, your code is responsible for choosing which accessory object to use.

For a given accessory object, only one session at a time is allowed for a specific protocol. The protocolStrings property of each EAAccessory object contains a dictionary whose keys are the supported protocols. If you attempt to create a session using a protocol that is already in use, the External Accessory framework generates an error.

Listing 8-1 shows a method that checks the list of connected accessories and grabs the first one that the application supports. It creates a session for the designated protocol and configures the input and output streams of the session. By the time this method returns the session object, it is connected to the accessory and ready to begin sending and receiving data.

Listing 8-1  Creating a communications session for an accessory

- (EASession *)openSessionForProtocol:(NSString *)protocolString
{
    NSArray *accessories = [[EAAccessoryManager sharedAccessoryManager]
                                   connectedAccessories];
    EAAccessory *accessory = nil;
    EASession *session = nil;
 
    for (EAAccessory *obj in accessories)
    {
        if ([[obj protocolStrings] containsObject:protocolString])
        {
            accessory = obj;
            break;
        }
    }
 
    if (accessory)
    {
        session = [[EASession alloc] initWithAccessory:accessory
                                 forProtocol:protocolString];
        if (session)
        {
            [[session inputStream] setDelegate:self];
            [[session inputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop]
                                     forMode:NSDefaultRunLoopMode];
            [[session inputStream] open];
            [[session outputStream] setDelegate:self];
            [[session outputStream] scheduleInRunLoop:[NSRunLoop currentRunLoop]
                                     forMode:NSDefaultRunLoopMode];
            [[session outputStream] open];
            [session autorelease];
        }
    }
 
    return session;
}

After the input and output streams are configured, the final step is to process the stream-related data. Listing 8-2 shows the fundamental structure of a delegate’s stream processing code. This method responds to events from both input and output streams of the accessory. As the accessory sends data to your application an event arrives indicating there are bytes available to be read. Similarly, when the accessory is ready to receive data from your application, events arrive indicating that fact. (Of course, your application does not always have to wait for an event to arrive before it can write bytes to the stream. It can also call the stream’s hasBytesAvailable method to see if the accessory is still able to receive data.) For more information on streams and handling stream-related events, see Stream Programming Guide for Cocoa.

Listing 8-2  Processing stream events

// Handle communications from the streams.
- (void)stream:(NSStream*)theStream handleEvent:(NSStreamEvent)streamEvent
{
    switch (streamEvent)
    {
        case NSStreamHasBytesAvailable:
            // Process the incoming stream data.
            break;
 
        case NSStreamEventHasSpaceAvailable:
            // Send the next queued command.
            break;
 
        default:
            break;
    }
 
}

Monitoring Accessory-Related Events

The External Accessory framework is capable of sending notifications whenever a hardware accessory is connected or disconnected. Although it is capable, it does not do so automatically. Your application must specifically request that notifications be generated by calling the registerForLocalNotifications method of the EAAccessoryManager class. When an accessory is connected, authenticated, and ready to interact with your application, the framework sends an EAAccessoryDidConnectNotification notification. When an accessory is disconnected, it sends an EAAccessoryDidDisconnectNotification notification. You can register to receive these notifications using the default NSNotificationCenter, and both notifications include information about which accessory was affected.

In addition to receiving notifications through the default notification center, an application that is currently interacting with an accessory can assign a delegate to the corresponding EAAccessory object and be notified of changes. Delegate objects must conform to the EAAccessoryDelegate protocol, which currently contains the optional accessoryDidDisconnect: method. You can use this method to receive disconnection notices without first setting up a notification observer.

For more information about how to register to receive notifications, see Notification Programming Topics for Cocoa.

Accessing Accelerometer Events

An accelerometer measures changes in velocity over time along a given linear path. The iPhone and iPod touch each contain three accelerometers, one along each of the primary axes of the device. This combination of accelerometers lets you detect movement of the device in any direction. You can use this data to track both sudden movements in the device and the device’s current orientation relative to gravity.

Note: In iPhone OS 3.0 and later, if you are trying to detect specific types of motion, such as shaking motions, you should consider tracking that motion using motion events instead of the accelerometer interfaces. Motion events offer a consistent way to detect specific types of accelerometer movement and are described in more detail in “Motion Events.”

Every application has a single UIAccelerometer object that can be used to receive acceleration data. You get the instance of this class using the sharedAccelerometer class method of UIAccelerometer. Using this object, you set the desired reporting interval and a custom delegate to receive acceleration events. You can set the reporting interval to be as small as 10 milliseconds, which corresponds to a 100 Hz update rate, although most applications can operate sufficiently with a larger interval. As soon as you assign your delegate object, the accelerometer starts sending it data. Thereafter, your delegate receives data at the requested update interval.

Listing 8-3 shows the basic steps for configuring the accelerometer. In this example, the update frequency is 50 Hz, which corresponds to an update interval of 20 milliseconds. The myDelegateObject is a custom object that you define; it must support the UIAccelerometerDelegate protocol, which defines the method used to receive acceleration data.

Listing 8-3  Configuring the accelerometer

#define kAccelerometerFrequency        50 //Hz
-(void)configureAccelerometer
{
    UIAccelerometer*  theAccelerometer = [UIAccelerometer sharedAccelerometer];
    theAccelerometer.updateInterval = 1 / kAccelerometerFrequency;
 
    theAccelerometer.delegate = self;
    // Delegate events begin immediately.
}

The shared accelerometer delivers event data at regular intervals to your delegate’s accelerometer:didAccelerate: method, shown in Listing 8-4. You can use this method to process the accelerometer data however you want. In general it is recommended that you use some sort of filter to isolate the component of the data in which you are interested.

Listing 8-4  Receiving an accelerometer event

- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration
{
    UIAccelerationValue x, y, z;
    x = acceleration.x;
    y = acceleration.y;
    z = acceleration.z;
 
    // Do something with the values.
}

To stop the delivery of acceleration events, set the delegate of the shared UIAccelerometer object to nil. Setting the delegate object to nil lets the system know that it can turn off the accelerometer hardware as needed, and thus save battery life.

The acceleration data you receive in your delegate method represents the instantaneous values reported by the accelerometer hardware. Even when a device is completely at rest, the values reported by this hardware can fluctuate slightly. When using these values, you should be sure to account for these fluctuations by averaging out the values over time or by calibrating the data you receive. For example, the Bubble Level sample application provides controls for calibrating the current angle against a known surface. Subsequent readings are then reported relative to the calibrated angle. If your own code requires a similar level of accuracy, you should also include some sort of calibration option in your user interface.

Choosing an Appropriate Update Interval

When configuring the update interval for acceleration events, it is best to choose an interval that minimizes the number of delivered events while still meeting the needs of your application. Few applications need acceleration events delivered 100 times a second. Using a lower frequency prevents your application from running as often and can therefore improve battery life. Table 8-3 lists some typical update frequencies and what you can do with the acceleration data generated at that frequency.

Table 8-3  Common update intervals for acceleration events

Event frequency (Hz)

Usage

10–20

Suitable for use in determining the vector representing the current orientation of the device.

30–60

Suitable for games and other applications that use the accelerometers for real-time user input.

70–100

Suitable for applications that need to detect high-frequency motion. For example, you might use this interval to detect the user hitting the device or shaking it very quickly.

Isolating the Gravity Component from Acceleration Data

If you are using the accelerometer data to detect the current orientation of a device, you need to be able to filter out the portion of the acceleration data that is caused by gravity from the portion that is caused by motion of the device. To do this, you can use a low-pass filter to reduce the influence of sudden changes on the accelerometer data. The resulting filtered values would then reflect the more constant effects of gravity.

Listing 8-5 shows a simplified version of a low-pass filter. This example uses a low-value filtering factor to generate a value that uses 10 percent of the unfiltered acceleration data and 90 percent of the previously filtered value. The previous values are stored in the accelX, accelY, and accelZ member variables of the class. Because acceleration data comes in regularly, these values settle out quickly and respond slowly to sudden but short-lived changes in motion.

Listing 8-5  Isolating the effects of gravity from accelerometer data

#define kFilteringFactor 0.1
 
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
    // Use a basic low-pass filter to keep only the gravity component of each axis.
    accelX = (acceleration.x * kFilteringFactor) + (accelX * (1.0 - kFilteringFactor));
    accelY = (acceleration.y * kFilteringFactor) + (accelY * (1.0 - kFilteringFactor));
    accelZ = (acceleration.z * kFilteringFactor) + (accelZ * (1.0 - kFilteringFactor));
 
   // Use the acceleration data.
}

Isolating Instantaneous Motion from Acceleration Data

If you are using accelerometer data to detect just the instant motion of a device, you need to be able to isolate sudden changes in movement from the constant effect of gravity. You can do that with a high-pass filter.

Listing 8-6 shows a simplified high-pass filter computation. The acceleration values from the previous event are stored in the accelX, accelY, and accelZ member variables of the class. This example computes the low-pass filter value and then subtracts it from the current value to obtain just the instantaneous component of motion.

Listing 8-6  Getting the instantaneous portion of movement from accelerometer data

#define kFilteringFactor 0.1
 
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration {
    // Subtract the low-pass value from the current value to get a simplified high-pass filter
    accelX = acceleration.x - ( (acceleration.x * kFilteringFactor) + (accelX * (1.0 - kFilteringFactor)) );
    accelY = acceleration.y - ( (acceleration.y * kFilteringFactor) + (accelY * (1.0 - kFilteringFactor)) );
    accelZ = acceleration.z - ( (acceleration.z * kFilteringFactor) + (accelZ * (1.0 - kFilteringFactor)) );
 
   // Use the acceleration data.
}

Getting the Current Device Orientation

If you need to know only the general orientation of the device, and not the exact vector of orientation, you should use the methods of the UIDevice class to retrieve that information. Using the UIDevice interface is simpler and does not require you to calculate the orientation vector yourself.

Before getting the current orientation, you must tell the UIDevice class to begin generating device orientation notifications by calling the beginGeneratingDeviceOrientationNotifications method. Doing so turns on the accelerometer hardware (which may otherwise be off to conserve power).

Shortly after enabling orientation notifications, you can get the current orientation from the orientation property of the shared UIDevice object. You can also register to receive UIDeviceOrientationDidChangeNotification notifications, which are posted whenever the general orientation changes. The device orientation is reported using the UIDeviceOrientation constants, which indicate whether the device is in landscape or portrait mode or whether the device is face up or face down. These constants indicate the physical orientation of the device and need not correspond to the orientation of your application’s user interface.

When you no longer need to know the orientation of the device, you should always disable orientation notifications by calling the endGeneratingDeviceOrientationNotifications method of UIDevice. Doing so gives the system the opportunity to disable the accelerometer hardware if it is not in use elsewhere.

Using Location and Heading Services

The Core Location framework provides support for locating the user’s current location and heading. This framework gathers information from the available onboard hardware and reports it to your application asynchronously. The availability of data is dependent on the type of device and whether or not the needed hardware is currently enabled, which it may not be if the device is in airplane mode.

To use the features of the Core Location framework, you must add CoreLocation.framework to your Xcode project and link against it in any relevant targets. To access the classes and headers of the framework, include an #import <CoreLocation/CoreLocation.h> statement at the top of any relevant source files. For more information on how to add frameworks to your project, see Files in Projects in Xcode Project Management Guide.

For general information about the classes of the Core Location framework, see Core Location Framework Reference.

Getting the User’s Current Location

The Core Location framework lets you locate the current position of the device and use that information in your application. The framework takes advantage of the device’s built-in hardware, triangulating a position fix from available signal information. It then reports the location to your code and occasionally updates that position information as it receives new or improved signals.

If you do use the Core Location framework, be sure to do so sparingly and to configure the location service appropriately. Gathering location data involves powering up the onboard radios and querying the available cell towers, Wi-Fi hotspots, or GPS satellites, which can take several seconds. In addition, requesting more accurate location data may require the radios to remain on for a longer period of time. Leaving this hardware on for extended periods of time can drain the device’s battery. Given that position information does not change too often, it is usually sufficient to establish an initial position fix and then acquire updates periodically after that. If you are sure you need regular position updates, you can also configure the service with a minimum threshold distance to minimize the number of position updates your code must process.

To retrieve the user’s current location, create an instance of the CLLocationManager class and configure it with the desired accuracy and threshold parameters. To begin receiving location notifications, assign a delegate to the object and call the startUpdatingLocation method to start the determination of the user’s current location. When new location data is available, the location manager notifies its assigned delegate object. If a location update has already been delivered, you can also get the most recent location data directly from the CLLocationManager object without waiting for a new event to be delivered.

Listing 8-7 shows implementations of a custom startUpdates method and the locationManager:didUpdateToLocation:fromLocation: delegate method. The startUpdates method creates a new location manager object (if one does not already exist) and uses it to start generating location updates. (In this case, the locationManager variable is a member variable declared by the MyLocationGetter class, which also conforms to the CLLocationManagerDelegate protocol.) The handler method uses the timestamp of the event to determine how recent it is. If it is an old event, the handler ignores it and waits for a more recent one, at which point it disables the location service.

Listing 8-7  Initiating and processing location updates

#import <CoreLocation/CoreLocation.h>
 
@implementation MyLocationGetter
- (void)startUpdates
{
    // Create the location manager if this object does not
    // already have one.
    if (nil == locationManager)
        locationManager = [[CLLocationManager alloc] init];
 
    locationManager.delegate = self;
    locationManager.desiredAccuracy = kCLLocationAccuracyKilometer;
 
    // Set a movement threshold for new events
    locationManager.distanceFilter = 500;
 
    [locationManager startUpdatingLocation];
}
 
 
// Delegate method from the CLLocationManagerDelegate protocol.
- (void)locationManager:(CLLocationManager *)manager
    didUpdateToLocation:(CLLocation *)newLocation
    fromLocation:(CLLocation *)oldLocation
{
    // If it's a relatively recent event, turn off updates to save power
    NSDate* eventDate = newLocation.timestamp;
    NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
    if (abs(howRecent) < 5.0)
    {
        [manager stopUpdatingLocation];
 
        printf("latitude %+.6f, longitude %+.6f\n",
                newLocation.coordinate.latitude,
                newLocation.coordinate.longitude);
    }
    // else skip the event and process the next one.
}
@end

Checking the timestamp of an event is recommended because the location service often returns the last cached location event immediately. It can take several seconds to obtain a rough location fix so the old data simply serves as a way to reflect the last known location. You can also use the accuracy as a means of determining whether you want to accept an event. As it receives more accurate data, the location service may return additional events, with the accuracy values reflecting the improvements accordingly.

Note: The Core Location framework records timestamp values at the beginning of each location query, not when that query returns. Because Core Location uses several different techniques to get a location fix, queries can sometimes come back in a different order than their timestamps might otherwise indicate. As a result, it is normal for new events to sometimes have timestamps that are slightly older than those from previous events. The framework concentrates on improving the accuracy of the location data with each new event it delivers, regardless of the timestamp values.

Getting Heading-Related Events

The Core Location framework supports two different ways to get heading-related information. Devices that contain GPS hardware can provide rough information about the current direction of travel using the same location events used to determine the user’s latitude and longitude. Devices that contain a magnetometer can provide more precise heading information through heading objects, which are instances of the CLHeading class.

The process for retrieving rough location events using GPS hardware is the same as the one described in “Getting the User’s Current Location.” The CLLocation object reported to your application delegate contains course and speed properties with the relevant information. This interface is appropriate for most applications that track the user’s movement over time, such as those that implement car-based navigation systems. For compass-based applications, or other applications that might involve knowing the user’s current heading when not in motion, you can ask the location manager to provide heading objects.

In order to receive heading objects, your application must be running on a device that contains a magnetometer. A magnetometer measures nearby magnetic fields emanating from the Earth and uses them to determine the precise orientation of the device. Although a magnetometer can be affected by local magnetic fields, such as those emanating from fixed magnets found in audio speakers, motors, and many other types of electronic devices, the Core Location framework is smart enough to filter out many local fields to ensure that heading objects contain useful data.

Note: If your application requires either course or heading information, you should include the UIRequiredDeviceCapabilities key in your application’s Info.plist file appropriately. This key lets you specify exactly which features your application requires in order to operate and can be used to require the presence of the GPS or magnetometer hardware. For more information about setting the value of this key, see “The Information Property List.”

To receive heading events, you create a CLLocationManager object, assign a delegate to it, and call the startUpdatingHeading method, as shown in Listing 8-8. Before asking for heading events, however, you should always check the headingAvailable property of the location manager to make sure that the appropriate hardware is present. If it is not, you should fall back to using location-based events to retrieve course information.

Listing 8-8  Initiating the delivery of heading events

CLLocationManager* locManager = [[CLLocationManager alloc] init];
if (locManager.headingAvailable)
{
   locManager.delegate = myDelegateObject; // Assign your custom delegate object
   locManager.headingFilter = 5;
   [locManager startUpdatingHeading];
}
else
   // Use location events instead

The object you assign to the delegate property must conform to the CLLocationManagerDelegate protocol. When a new heading event arrives, the location manager object calls the locationManager:didUpdateHeading: method to deliver that event to your application. Upon receiving a new event, you should check the headingAccuracy property to ensure that the data you just received is valid, as shown in Listing 8-9.

Listing 8-9  Processing heading events

- (void)locationManager:(CLLocationManager*)manager didUpdateHeading:(CLHeading*)newHeading
{
   // If the accuracy is valid, go ahead and process the event.
   if (newHeading.headingAccuracy > 0)
   {
      CLLocationDirection theHeading = newHeading.magneticHeading;
 
      // Do something with the event data.
   }
}

The magneticHeading property of a CLHeading object contains the main heading data and is always present. This property gives you a heading measurement relative to magnetic North, which is not at the same location as the north pole. If you want a heading relative to the north pole (also known as geographical North), you must call the startUpdatingLocation method to start the delivery of location updates before you call startUpdatingHeading. You can then use the trueHeading property of the CLHeading object to obtain the heading to geographical North.

Displaying Maps and Annotations

Introduced in iPhone OS 3.0, the Map Kit framework lets you embed a fully functional map interface into your application window. The map support provided by this framework includes many of the features normally found in the Maps application. You can display standard street-level map information, satellite imagery, or a combination of the two. You can zoom and pan the map programmatically, and the framework provides automatic support for the touch events that let users zoom and pan the map. You can annotate the map with custom information. And you can use the reverse geocoder feature of the framework to find the address associated with map coordinates.

To use the features of the Map Kit framework, you must add MapKit.framework to your Xcode project and link against it in any relevant targets. To access the classes and headers of the framework, include an #import <MapKit/MapKit.h> statement at the top of any relevant source files. For more information on how to add frameworks to your project, see Files in Projects in Xcode Project Management Guide. For general information about the classes of the Map Kit framework, see MapKit Framework Reference.

Important: The Map Kit framework uses Google services to provide map data. Use of the framework and its associated interfaces binds you to the Google Maps/Google Earth API terms of service. You can find these terms of service at http://code.google.com/apis/maps/iphone/terms.html.

Adding a Map View to Your User Interface

To add maps to your application, you embed an instance of the MKMapView class into your application’s view hierarchy. This class provides support both for displaying the map information and for managing the user interactions with that information. You can create an instance of this class programmatically (initializing it with the initWithFrame: method) or use Interface Builder to add it to one of your nib files.

Because it is a view, you can use the frame property of a map view to move and resize it however you like within your view hierarchy. Although the map view does not have any controls of its own, you can layer toolbars and other views on top of it to add controls for interacting with the map contents. Any subviews you add to the map view remain fixed in place and do not scroll with the map contents. If you want to add custom content to the map itself, and have that content scroll with the map, you must create annotations as described in “Displaying Annotations.”

The MKMapView class has a handful of properties that you can configure before displaying it. The most important property to set is the region property. This property defines the portion of the map that should be displayed initially and is how you zoom and pan the contents of the map.

Zooming and Panning the Map Content

The region property of the MKMapView class controls the portion of the map that is displayed at any given time. When you want to zoom or pan the map, all you have to do is change the values in this property appropriately. The property consists of an MKCoordinateRegion structure, which has the following definition:

typedef struct {
   CLLocationCoordinate2D center;
   MKCoordinateSpan span;
} MKCoordinateRegion;

To pan the map to a new location, you change the values in the center field. To zoom in and out, you change the values in the span field. You specify the values for these fields using map coordinates, which are measured in degrees, minutes, and seconds. For the span field, the values you specify represent latitudinal and longitudinal distances. Although latitudinal distances are relatively fixed at approximately 111 kilometers per degree, longitudinal distances vary with the latitude. At the equator, longitudinal values are approximately 111 kilometers per degree but at the poles they approach zero. Of course, you can always use the MKCoordinateRegionMakeWithDistance function to create a region using kilometer values instead of degrees.

To update the map without animating your changes, you can modify the region or centerCoordinate property directly. To animate your changes, you must use either the setRegion:animated: or setCenterCoordinate:animated: method. The setCenterCoordinate:animated: method lets you pan the map without zooming in or out inadvertently. The setRegion:animated: method lets you pan and zoom at the same time. For example, to pan the map to the left by half the current map width, you could use the following code to find the coordinate at the left edge of the map and use that as the new center point, as shown here:

CLLocationCoordinate2D mapCenter = myMapView.centerCoordinate;
mapCenter = [myMapView convertPoint:
               CGPointMake(1, (myMapView.frame.size.height/2.0))
               toCoordinateFromView:myMapView];
[myMapView setCenterCoordinate:mapCenter animated:YES];

To zoom in and out, instead of modifying the center coordinate, you modify the span. To zoom in, you decrease the span. To zoom out, you increase it. In other words if the current span is one degree, specifying a span of two degrees zooms out by a factor of two:

MKCoordinateRegion theRegion = myMapView.region;
 
// Zoom out
theRegion.span.longitudeDelta *= 2.0;
theRegion.span.latitudeDelta *= 2.0;
[myMapView setRegion:theRegion animated:YES];

Displaying the User’s Current Location

The Map Kit framework includes built-in support for displaying the user’s current location on the map. To show this location, set the showsUserLocation property of your map view object to YES. Doing so causes the map view to use the Core Location framework to find the user’s location and add an annotation of type MKUserLocation to the map.

The addition of the MKUserLocation annotation object to the map is reported by the delegate the same way custom annotations are. If you want to associate a custom annotation view with the user’s location, you should return that view from your delegate object’s mapView:viewForAnnotation: method. If you want to use the default annotation view, you should return nil from that method instead.

Converting Between Coordinates and Pixels

Although you normally specify points on the map using latitude and longitude values, there may be times when you need to convert to and from pixels in the map view object itself. For example, if you allow the user to drag annotations around the map surface, the event handlers for your custom annotation views would need to convert frame coordinates to map coordinates so that they could update the associated annotation object. The MKMapView class includes several routines for converting back and forth between map coordinates and the local coordinate system of the map view object itself, including:

For more information about handling events in your custom annotations, see “Handling Events in an Annotation View.”

Displaying Annotations

Annotations are pieces of map content that you define and layer on top of the map itself. The Map Kit framework implements annotations in two parts: an annotation object and a view to display that annotation. For the most part, you are responsible for providing these custom objects. However, the framework does provide standard annotations and views that you can use as well.

Displaying annotations in your map view is a two-step process:

  1. Create your annotation object and add it to your map view.

  2. Implement the mapView:viewForAnnotation: method of your delegate object and create the corresponding annotation view there.

An annotation object is any object that conforms to the MKAnnotation protocol. Typically annotation objects are relatively small data objects that store the coordinate of the annotation and the relevant information about the annotation, such as its name. Because annotations are defined using a protocol, any object in your application can become one. In practice though, you should make your annotation objects lightweight, because the map view keeps references to them until you remove them explicitly; however, the same is not necessarily true of annotation views.

When an annotation needs to be displayed on screen, the map view is responsible for making sure the annotation object has an associated annotation view. It does this by calling the the mapView:viewForAnnotation: method of its delegate object when the annotation’s coordinate is about to become visible on the screen. Because annotation views tend to be more heavyweight objects than their corresponding annotation objects, though, the map view tries not to keep too many annotation views around in memory at the same time. To do this, it implements a recycling scheme for annotation views that is similar to how table views recycle table cells during scrolling. When an annotation view moves off screen, the map view can disassociate that view from its annotation object and put it on a reuse queue. Before creating a new annotation view object, your delegate’s mapView:viewForAnnotation: method should always check this reuse queue to see if an existing view is available by calling the map view’s dequeueReusableAnnotationViewWithIdentifier: method. If that method returns a valid view object, you can reinitialize the view and return it; otherwise, you should create and return a new view object.

Adding and Removing Annotation Objects

You do not add annotation views to the map directly. Instead, you add annotation objects, which are typically not views. An annotation object is any object in your application that conforms to the MKAnnotation protocol. The most important part of any annotation object is its coordinate property, which is a required property of the MKAnnotation protocol and provides the anchor point for the annotation on the map.

To add an annotation to the map view, all you have to do is call the addAnnotation: or addAnnotations: method of the map view object. It is up to you to decide when it is appropriate to add annotations to the map view and to provide the user interface for doing so. You can provide a toolbar with commands that allow the user to create new annotations or you can create the annotations programmatically yourself, perhaps using a local or remote database of information.

If your application needs to dispose of an old annotation, you should always remove it from the map using the removeAnnotation: or removeAnnotations: method before deleting it. Because the map view shows all annotations that it knows about, you need to remove annotations explicitly if you do not want them displayed on the map. For example, if your application allows the user to filter a list of restaurants or local sights, you would need to remove any annotations that did not match the filter criteria.

Defining Annotation Views

The Map Kit framework provides two annotation view classes: MKAnnotationView and MKPinAnnotationView. The MKAnnotationView class is a concrete view that defines the basic behavior for all annotation views. The MKPinAnnotationView class is a subclass of MKAnnotationView that displays one of the standard system pin images at the associated annotation’s coordinate point.

You can use the MKAnnotationView class as is to display simple annotations or you can subclass it to provide more interactive behavior. When using the class as is, you provide a custom image to represent the content you want to display on the map and assign it to the image property of the annotation view. Using the class this way is perfect for situations where your you do not display dynamically changing content and do not support any user interactivity. However, if you do want dynamic content or user interactivity, you must define a custom subclass.

In a custom subclass, you can draw dynamic content in one of two ways. You can continue to use the image property to display images for the annotation, perhaps setting up a timer so that you can change the current image at regular intervals. You can also override the view’s drawRect: method and draw your content explicitly, again setting up a timer so that you can call the view’s setNeedsDisplay method periodically.

When drawing content using the drawRect: method, you must always remember to specify the size of your annotation view shortly after initialization. The default initialization method for annotation views does not take a frame rectangle as a parameter. Instead, it uses the image you specify in the image property to set that frame size later. If you do not set an image, though, you must set the frame size explicitly in order for your rendered content to be visible.

For information on how to support user interactivity in your annotation views, see “Handling Events in an Annotation View.” For information on how to set up timers, see Timer Programming Topics for Cocoa.

Creating Annotation Views

You always create annotation views in the mapView:viewForAnnotation: method of your delegate object. Before creating a new view, you should always check to see if there is an existing view waiting to be used by calling the dequeueReusableAnnotationViewWithIdentifier: method. If this method returns a non-nil value, you should assign the annotation provided by the map view to the view’s annotation property, perform any additional configuration needed to put the view in a known state, and return it. If the method returns nil, you should proceed with creating and returning a new annotation view object.

Listing 8-10 shows a sample implementation of the mapView:viewForAnnotation: method. This method provides pin annotation views for custom annotation objects. If an existing pin annotation view already exists, this method associates the annotation object to that view. If no view is in the reuse queue, this method creates a new one, setting up the basic properties of the view and configuring an accessory view for the annotation’s callout.

Listing 8-10  Creating annotation views

- (MKAnnotationView *)mapView:(MKMapView *)mapView
                      viewForAnnotation:(id <MKAnnotation>)annotation
{
    // If it's the user location, just return nil.
    if ([annotation isKindOfClass:[MKUserLocation class]])
        return nil;
 
    // Handle any custom annotations.
    if ([annotation isKindOfClass:[CustomPinAnnotation class]])
    {
        // Try to dequeue an existing pin view first.
        MKPinAnnotationView*    pinView = (MKPinAnnotationView*)[mapView
        dequeueReusableAnnotationViewWithIdentifier:@"CustomPinAnnotation"];
 
        if (!pinView)
        {
            // If an existing pin view was not available, create one
           pinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation
                       reuseIdentifier:@"CustomPinAnnotation"]
                             autorelease];
            pinView.pinColor = MKPinAnnotationColorRed;
            pinView.animatesDrop = YES;
            pinView.canShowCallout = YES;
 
            // Add a detail disclosure button to the callout.
            UIButton* rightButton = [UIButton buttonWithType:
                               UIButtonTypeDetailDisclosure];
            [rightButton addTarget:self action:@selector(myShowDetailsMethod:)
                               forControlEvents:UIControlEventTouchUpInside];
            pinView.rightCalloutAccessoryView = rightButton;
        }
        else
            pinView.annotation = annotation;
 
        return pinView;
    }
 
    return nil;
}

Handling Events in an Annotation View

Although they live in a special layer above the map content, annotation views are full-fledged views capable of receiving touch events. You can use these events to provide more user interactivity for your annotations. For example, you could use touch events in the view to drag the view around the surface of the map.

Note: Because maps are displayed in a scrolling interface, there is typically a short delay between the time the user touches your custom view and the corresponding events are delivered. This delay gives the underlying scroll view a chance to determine if the touch event is part of a scrolling gesture.

The following sequence of code listings show you how to implement a user-movable annotation view. The annotation view displays a bullseye image directly over the annotation’s coordinate point and includes a custom accessory view for displaying details about the target. Figure 8-1 shows an instance of this annotation view, along with its callout bubble.

Figure 8-1  The bullseye annotation view

The bullseye annotation view

Listing 8-11 shows the definition of the BullseyeAnnotationView class. The class includes some additional member variables that it uses during tracking to move the view correctly. It also stores a pointer to the map view itself, the value for which is set by the code in the mapView:viewForAnnotation: method when it creates or reinitializes the annotation view. The map view object is needed when event tracking is finished to adjust the map coordinate of the annotation object.

Listing 8-11  The BullseyeAnnotationView class

@interface BullseyeAnnotationView : MKAnnotationView
{
    BOOL isMoving;
    CGPoint startLocation;
    CGPoint originalCenter;
 
    MKMapView* map;
}
 
@property (assign,nonatomic) MKMapView* map;
 
- (id)initWithAnnotation:(id <MKAnnotation>)annotation;
 
@end
 
@implementation BullseyeAnnotationView
@synthesize map;
- (id)initWithAnnotation:(id <MKAnnotation>)annotation
{
    self = [super initWithAnnotation:annotation
               reuseIdentifier:@"BullseyeAnnotation"];
    if (self)
    {
        UIImage*    theImage = [UIImage imageNamed:@"bullseye32.png"];
        if (!theImage)
            return nil;
 
        self.image = theImage;
        self.canShowCallout = YES;
        self.multipleTouchEnabled = NO;
        map = nil;
 
        UIButton*    rightButton = [UIButton buttonWithType:
                       UIButtonTypeDetailDisclosure];
        [rightButton addTarget:self action:@selector(myShowAnnotationAddress:)
                       forControlEvents:UIControlEventTouchUpInside];
        self.rightCalloutAccessoryView = rightButton;
    }
    return self;
}
@end

When a touch event first arrives in a bullseye view, the touchesBegan:withEvent: method of that class records information about the initial touch event, as shown in Listing 8-12. It uses this information later in its touchesMoved:withEvent: method to adjust the position of the view. All location information is stored in the coordinate space of the superview.

Listing 8-12  Tracking the view’s location

@implementation BullseyeAnnotationView (TouchBeginMethods)
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // The view is configured for single touches only.
    UITouch* aTouch = [touches anyObject];
    startLocation = [aTouch locationInView:[self superview]];
    originalCenter = self.center;
 
    [super touchesBegan:touches withEvent:event];
}
 
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    UITouch* aTouch = [touches anyObject];
    CGPoint newLocation = [aTouch locationInView:[self superview]];
    CGPoint newCenter;
 
    // If the user's finger moved more than 5 pixels, begin the drag.
    if ( (abs(newLocation.x - startLocation.x) > 5.0) ||
         (abs(newLocation.y - startLocation.y) > 5.0) )
         isMoving = YES;
 
    // If dragging has begun, adjust the position of the view.
    if (isMoving)
    {
        newCenter.x = originalCenter.x + (newLocation.x - startLocation.x);
        newCenter.y = originalCenter.y + (newLocation.y - startLocation.y);
        self.center = newCenter;
    }
    else    // Let the parent class handle it.
        [super touchesMoved:touches withEvent:event];
}
@end

When the user stops dragging an annotation view, you need to adjust the coordinate of the original annotation to ensure the view remains in the new position. Listing 8-13 shows the touchesEnded:withEvent: method for the BullseyeAnnotationView class. This method uses the map member variable to convert the pixel-based point into a map coordinate value. Because the coordinate property of an annotation is normally read-only, the annotation object in this case implements a custom changeCoordinate method to update the value it stores locally and reports using the coordinate property. If the touch event was cancelled for some reason, the touchesCancelled:withEvent: method returns the annotation view to its original position.

Listing 8-13  Handling the final touch events

@implementation BullseyeAnnotationView (TouchEndMethods)
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    if (isMoving)
    {
        // Update the map coordinate to reflect the new position.
        CGPoint newCenter = self.center;
        BullseyeAnnotation* theAnnotation = self.annotation;
        CLLocationCoordinate2D newCoordinate = [map convertPoint:newCenter
                           toCoordinateFromView:self.superview];
 
        [theAnnotation changeCoordinate:newCoordinate];
 
        // Clean up the state information.
        startLocation = CGPointZero;
        originalCenter = CGPointZero;
        isMoving = NO;
    }
    else
        [super touchesEnded:touches withEvent:event];
}
 
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    if (isMoving)
    {
        // Move the view back to its starting point.
        self.center = originalCenter;
 
        // Clean up the state information.
        startLocation = CGPointZero;
        originalCenter = CGPointZero;
        isMoving = NO;
    }
    else
        [super touchesCancelled:touches withEvent:event];
}
@end

Getting Placemark Information from the Reverse Geocoder

The Map Kit framework deals primarily with map coordinate values, which consist of a latitude and longitude pair. Although map coordinates are good for code, they are not always the easiest things for users to understand. To make it easier for users, you can use the MKReverseGeocoder class to obtain placemark information such as the street address, city, state, and country associated with a given map coordinate.

The MKReverseGeocoder class queries the underlying map service for information about the map coordinate you specify. Because it requires access to the network, a reverse geocoder object always performs its query asynchronously and reports its results back using the associated delegate object. The delegate for a reverse geocoder must conform to the MKReverseGeocoderDelegate protocol.

To start a reverse geocoder query, create an instance of the MKReverseGeocoder, class, assign an appropriate object to the delegate property, and call the start method. If the query completes successfully, your delegate receives an MKPlacemark object with the results. Placemark objects are themselves annotation objects—that is, they adopt the MKAnnotation protocol—so you can add them to your map view’s list of annotations if you want.

The Google terms of service require that the MKReverseGeocoder class be used in conjunction with a Google map. In addition, each application that uses the Map Kit framework has a limited amount of reverse geocoding capacity, so it is to your advantage to use reverse geocode requests sparingly. Here are some rules of thumb to apply when creating your requests:

Taking Pictures with the Camera

UIKit provides access to a device’s camera through the UIImagePickerController class. This class displays the standard system interface for taking pictures using the available camera. It also supports optional controls for resizing and cropping the image after the user takes it. This class can also be used to select photos from the user’s photo library.

The view representing the camera interface is a modal view that is managed by the UIImagePickerController class. You should never access this view directly from your code. To display it, you must call the presentModalViewController:animated: method of the currently active view controller, passing a UIImagePickerController object as the new view controller. Upon being installed, the picker controller automatically slides the camera interface into position, where it remains active until the user approves the picture or cancels the operation. At that time, the picker controller notifies its delegate of the user’s choice.

Interfaces managed by the UIImagePickerController class may not be available on all devices. Before displaying the camera interface, you should always make sure that the interface is available by calling the isSourceTypeAvailable: class method of the UIImagePickerController class. You should always respect the return value of this method. If this method returns NO, it means that the current device does not have a camera or that the camera is currently unavailable for some reason. If the method returns YES, you display the camera interface by doing the following:

  1. Create a new UIImagePickerController object.

  2. Assign a delegate object to the picker controller.

    In most cases, the current view controller acts as the delegate for the picker, but you can use an entirely different object if you prefer. The delegate object must conform to the UIImagePickerControllerDelegate and UINavigationControllerDelegate protocols.

    Note: If your delegate does not conform to the UINavigationControllerDelegate protocol, you may see a warning during compilation. However, because the methods of this protocol are optional, the warning has no impact on your code. To eliminate the warning, you can include the UINavigationControllerDelegate protocol in the list of supported protocols for your delegate’s class. You do not need to implement the corresponding methods.

  3. Set the picker type to UIImagePickerControllerSourceTypeCamera.

  4. Optionally, enable or disable the picture editing controls by assigning an appropriate value to the allowsImageEditing property.

  5. Call the presentModalViewController:animated: method of the current view controller to display the picker.

Listing 8-14 shows the code representing the preceding set of steps. As soon as you call the presentModalViewController:animated method, the picker controller takes over, displaying the camera interface and responding to all user interactions until the interface is dismissed. To choose an existing photo from the user’s photo library, all you have to do is change the value in the sourceType property of the picker to UIImagePickerControllerSourceTypePhotoLibrary.

Listing 8-14  Displaying the interface for taking pictures

-(BOOL)startCameraPickerFromViewController:(UIViewController*)controller usingDelegate:(id<UIImagePickerControllerDelegate>)delegateObject
{
    if ( (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
            || (delegateObject == nil) || (controller == nil))
        return NO;
 
    UIImagePickerController* picker = [[UIImagePickerController alloc] init];
    picker.sourceType = UIImagePickerControllerSourceTypeCamera;
    picker.delegate = delegateObject;
    picker.allowsImageEditing = YES;
 
    // Picker is displayed asynchronously.
    [controller presentModalViewController:picker animated:YES];
    return YES;
}

When the user taps the appropriate button to dismiss the camera interface, the UIImagePickerController notifies the delegate of the user’s choice but does not dismiss the interface. The delegate is responsible for dismissing the picker interface. (Your application is also responsible for releasing the picker when done with it, which you can do in the delegate methods.) It is for this reason that the delegate is actually the view controller object that presented the picker in the first place. Upon receiving the delegate message, the view controller would call its dismissModalViewControllerAnimated: method to dismiss the camera interface.

Listing 8-15 shows the delegate methods for dismissing the camera interface displayed in Listing 8-14. These methods are implemented by a custom MyViewController class, which is a subclass of UIViewController and, for this example, is considered to be the same object that displayed the picker in the first place. The useImage: method is an empty placeholder for the work you would do in your own version of this class and should be replaced by your own custom code.

Listing 8-15  Delegate methods for the image picker

@implementation MyViewController (ImagePickerDelegateMethods)
 
- (void)imagePickerController:(UIImagePickerController *)picker
                    didFinishPickingImage:(UIImage *)image
                    editingInfo:(NSDictionary *)editingInfo
{
    [self useImage:image];
 
    // Remove the picker interface and release the picker object.
    [[picker parentViewController] dismissModalViewControllerAnimated:YES];
    [picker release];
}
 
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    [[picker parentViewController] dismissModalViewControllerAnimated:YES];
    [picker release];
}
 
// Implement this method in your code to do something with the image.
- (void)useImage:(UIImage*)theImage
{
}
@end

If image editing is enabled and the user successfully picks an image, the image parameter of the imagePickerController:didFinishPickingImage:editingInfo: method contains the edited image. You should treat this image as the selected image, but if you want to store the original image, you can get it (along with the crop rectangle) from the dictionary in the editingInfo parameter.

Picking a Photo from the Photo Library

UIKit provides access to the user’s photo library through the UIImagePickerController class. This controller displays a photo picker interface, which provides controls for navigating the user’s photo library and selecting an image to return to your application. You also have the option of enabling user editing controls, which let the user the pan and crop the returned image. This class can also be used to present a camera interface.

Because the UIImagePickerController class is used to display the interface for both the camera and the user’s photo library, the steps for using the class are almost identical for both. The only difference is that you assign the UIImagePickerControllerSourceTypePhotoLibrary value to the sourceType property of the picker object. The steps for displaying the camera picker are discussed in “Taking Pictures with the Camera.”

Note: As you do for the camera picker, you should always call the isSourceTypeAvailable: class method of the UIImagePickerController class and respect the return value of the method. You should never assume that a given device has a photo library. Even if the device has a library, this method could still return NO if the library is currently unavailable.

Using the Mail Composition Interface

In iPhone OS 3.0 and later, you can use the MFMailComposeViewController class to present a standard mail composition interface inside your own applications. Prior to displaying the interface, you use the methods of the class to configure the email recipients, the subject, body, and any attachments you want to include. Upon posting the interface (using the standard view controller techniques), the user has the option of editing the email contents before submitting the email to the Mail application for delivery. The user also has the right to cancel the email altogether.

Note: In all versions of iPhone OS, you can also compose email messages by creating and opening a URL that uses the mailto scheme. URLs of this type are automatically handled by the Mail application. For more information on how to open a URL of this type, see “Communicating with Other Applications.”

To use the mail composition interface, you must add MessageUI.framework to your Xcode project and link against it in any relevant targets. To access the classes and headers of the framework, include an #import <MessageUI/MessageUI.h> statement at the top of any relevant source files. For information on how to add frameworks to your project, see Files in Projects in Xcode Project Management Guide.

To use the MFMailComposeViewController class in your application, you create an instance and use its methods to set the initial email data. You must also assign an object to the mailComposeDelegate property of the view controller to handle the dismissal of the interface when the user accepts or cancels the email. The delegate object you specify must conform to the MFMailComposeViewControllerDelegate protocol.

When specifying email addresses for the mail composition interface, you specify plain string objects. If you want to use email addresses from the user’s list of contacts, you can use the Address Book framework to retrieve that information. For more information on how to get email and other information using this framework, see Address Book Programming Guide for iPhone OS.

Listing 8-16 shows the code for creating the MFMailComposeViewController object and displaying the mail composition interface modally in your application. You would include the displayComposerSheet method in one of your custom view controllers and call the method as needed to display the interface. In this example, the parent view controller assigns itself as the delegate and implements the mailComposeController:didFinishWithResult:error: method. The delegate method dismisses the delegate without taking any further actions. In your own application, you could use the delegate to track whether the user sent or canceled the email by examining the value in the result parameter.

Listing 8-16  Posting the mail composition interface

@implementation WriteMyMailViewController (MailMethods)
 
-(void)displayComposerSheet
{
    MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
    picker.mailComposeDelegate = self;
 
    [picker setSubject:@"Hello from California!"];
 
    // Set up the recipients.
    NSArray *toRecipients = [NSArray arrayWithObjects:@"first@example.com",
                                   nil];
    NSArray *ccRecipients = [NSArray arrayWithObjects:@"second@example.com",
                                   @"third@example.com", nil];
    NSArray *bccRecipients = [NSArray arrayWithObjects:@"four@example.com",
                                   nil];
 
    [picker setToRecipients:toRecipients];
    [picker setCcRecipients:ccRecipients];
    [picker setBccRecipients:bccRecipients];
 
    // Attach an image to the email.
    NSString *path = [[NSBundle mainBundle] pathForResource:@"ipodnano"
                                 ofType:@"png"];
    NSData *myData = [NSData dataWithContentsOfFile:path];
    [picker addAttachmentData:myData mimeType:@"image/png"
                                 fileName:@"ipodnano"];
 
    // Fill out the email body text.
    NSString *emailBody = @"It is raining in sunny California!";
    [picker setMessageBody:emailBody isHTML:NO];
 
    // Present the mail composition interface.
    [self presentModalViewController:picker animated:YES];
    [picker release]; // Can safely release the controller now.
}
 
// The mail compose view controller delegate method
- (void)mailComposeController:(MFMailComposeViewController *)controller
              didFinishWithResult:(MFMailComposeResult)result
              error:(NSError *)error
{
    [self dismissModalViewControllerAnimated:YES];
}
@end

For more information on the standard view controller techniques for displaying interfaces, see View Controller Programming Guide for iPhone OS. For information about the classes of the Message UI framework, see Message UI Framework Reference.



Last updated: 2009-10-19

Did this document help you? Yes It's good, but... Not helpful...