If the with Flags parameter contains the value AVAudioSessionInterruptionFlags_ShouldResume, you can immediately resume the playback of your audio player using the play instance method o
Trang 1Incoming phone calls cannot be simulated with iPhone Simulator You
must always test your applications on a real device.
When an interruption occurs, the audioPlayerBeginInterruption: delegate method of
an AVAudioPlayer instance will be called Here, your audio session has been deactivated.
In case of a phone call, the user can just hear her ring tone When the interruption ends (the phone call is finished or the user rejects the call), the audioPlayerEndInterrup tion:withFlags: delegate method of your AVAudioPlayer will be invoked If the with Flags parameter contains the value AVAudioSessionInterruptionFlags_ShouldResume, you can immediately resume the playback of your audio player using the play instance method of AVAudioPlayer.
The playback of audio files using AVAudioPlayer might show memory
leaks in Instruments when the application is being run on iPhone
Sim-ulator Testing the same application on an iOS device proves that the
memory leaks are unique to the simulator, not the device I strongly
suggest that you run, test, debug, and optimize your applications on real
devices before releasing them on the App Store.
9.3 Recording Audio
Problem
You want to be able to record audio files on an iOS device.
Trang 2Make sure you have added the CoreAudio.framework framework to your target file, and use the AVAudioRecorder class in the AV Foundation framework:
NSError *error = nil;
NSString *pathAsString = [self audioRecordingPath];
NSURL *audioRecordingURL = [NSURL fileURLWithPath:pathAsString];
self.audioRecorder = [[AVAudioRecorder alloc]
The AVAudioRecorder class in the AV Foundation framework facilitates audio recording
in iOS applications To start a recording, you need to pass various pieces of information
to the initWithURL:settings:error: instance method of AVAudioRecorder:
The URL of the file where the recording should be saved
This is a local URL The AV Foundation framework will decide which audio format should be used for the recording based on the file extension provided in this URL,
so choose the extension carefully.
The settings that must be used before and while recording
Examples include the sampling rate, channels, and other information that will help the audio recorder start the recording This is a dictionary object.
The address of an instance of NSError where any initialization errors should be saved to
The error information could be valuable later, and you can retrieve it from this instance method in case something goes wrong.
The settings parameter of the initWithURL:settings:error: method is particularly interesting There are many keys that could be saved in the settings dictionary, but we will discuss only some of the most important ones in this recipe:
Trang 3The number of channels that must be used for the recording.
AVEncoderAudioQualityKey
The quality with which the recording must be made Some of the values that can
be specified for this key are:
1 Start recording audio in Apple Lossless format.
2 Save the recording into a file named Recording.m4a in our application’s ments directory.
Docu-3 Five seconds after the recording starts, finish the recording process and ately start playing the file into which we recorded the audio input.
immedi-We will start by declaring the required properties in the h file of a simple view
@property (nonatomic, strong) AVAudioRecorder *audioRecorder;
@property (nonatomic, strong) AVAudioPlayer *audioPlayer;
- (NSString *) audioRecordingPath;
- (NSDictionary *) audioRecordingSettings;
@end
When the view inside our view controller is loaded for the first time, we will attempt
to start the recording process and then stop the process, if successfully started, after five seconds:
Trang 4In the viewDidLoad method of our view controller, we attempt to instantiate an object
of type AVAudioRecorder and assign it to the audioRecorder property that we declared
in the h file of the same view controller earlier.
Trang 5We are using an instance method called audioRecordingPath to determine the NSString representation of the local URL where we want to store our recording This method is implemented like so:
pa (NSDictionary *) audioRecordingSettings{
NSDictionary *result = nil;
/* Let's prepare the audio recorder options in the dictionary
Later we will use this dictionary to instantiate an audio
recorder of type AVAudioRecorder */
Trang 6When the audio recording has actually stopped, we will attempt to play what was recorded:
/* Let's try to retrieve the data for the recorded file */
NSError *playbackError = nil;
/* Form an audio player and make it play the recorded data */
self.audioPlayer = [[AVAudioPlayer alloc] initWithData:fileData
error:&playbackError];
Trang 7/* Prepare to play and start playing */
if ([self.audioPlayer prepareToPlay] &&
As explained in Recipe 9.2 , when playing audio files using AVAudioPlayer, we also need
to handle interruptions, such as incoming phone calls, when deploying our application
on an iOS device and before releasing the application on the App Store:
- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)player{
/* The audio session has been deactivated here */
Trang 9Just like audio players (instances of AVAudioPlayer), audio recorders of type AVAudio Recorder also receive delegate messages whenever the audio session associated with them is deactivated because of an interruption The two methods mentioned in this recipe’s Solution are the best places to handle such interruptions In the case of an interruption to the audio recorder, you can invoke the record instance method of AVAudioRecorder after the interruption to continue the recording process However, the recording will overwrite the previous recording and all data that was recorded before the interruption will be lost.
It is very important to bear in mind that when the delegate of your audio
recorder receives the audioRecorderBeginInterruption: method, the
audio session has already been deactivated, and invoking the resume
instance method will not work on your audio recorder After the
inter-ruption is ended, you must invoke the record instance method of your
AVAudioRecorder to resume recording.
9.5 Playing Audio over Other Active Sounds
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
After retrieving an instance of the AVAudioSession class, you can invoke the setCate gory:error: instance method of the audio session object to choose among the different categories available to iOS applications Different values that can be set as the audio session category of an application are listed here:
AVAudioSessionCategorySoloAmbient
This category is exactly like the AVAudioSessionCategoryAmbient category, except that this category will stop the audio playback of all other applications, such as the iPod When the device is put into silent mode, your audio playback will be paused.
Trang 10This also happens when the screen is locked This is the default category that iOS chooses for an application.
AVAudioSessionCategoryRecord
This stops other applications’ audio (e.g., the iPod) and also will not allow your application to initiate an audio playback (e.g., using AVAudioPlayer) You can only record audio in this mode Using this category, calling the prepareToPlay instance method of AVAudioPlayer will return YES and the play instance method will return
NO The main UI interface will function as usual The recording of your application will continue even if the iOS device’s screen is locked by the user.
AVAudioSessionCategoryPlayback
This category will silence other applications’ audio playback (such as the audio playback of iPod applications) You can then use the prepareToPlay and play in- stance methods of AVAudioPlayer to play a sound in your application The main UI thread will function as normal The audio playback will continue even if the screen
is locked by the user and even if the device is in silent mode.
AVAudioSessionCategoryPlayAndRecord
This category allows audio to be played and recorded at the same time in your application This will stop the audio playback of other applications when your audio recording or playback begins The main UI thread of your application will function as normal The playback and the recording will continue even if the screen
is locked or the device is in silent mode.
AVAudioSessionCategoryAudioProcessing
This category can be used for applications that do audio processing, but not audio playback or recording By setting this category, you cannot play or record any audio in your application Calling the prepareToPlay and play instance methods of AVAudioPlayer will return NO Audio playback of other applications, such as the iPod, will also stop if this category is set.
AVAudioSessionCategoryAmbient
This category will not stop the audio from other applications, but it will allow you
to play audio over the audio being played by other applications, such as the iPod The main UI thread of your application will function normally The prepareTo Play and play instance methods of AVAudioPlayer will return with the value YES The audio being played by your application will stop when the user locks the screen The silent mode silences the audio playback of your application only if your application is the only application playing an audio file If you start playing audio while the iPod is playing a song, putting the device in silent mode does not stop your audio playback.
To give you an example of using AVAudioSession, let’s start an audio player that will
play its audio file over other applications’ audio playback We will begin with the h
file of a view controller:
#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
Trang 11NSError *audioSessionError = nil;
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
Trang 139.6 Playing Video Files
Problem
You would like to be able to play video files in your iOS application.
Solution
Use an instance of the MPMoviePlayerController class.
If you simply want to display a full-screen movie player, you can use the
MPMoviePlayerViewController class and push your movie player view
controller into the stack of view controllers of a navigation controller
(for instance), or simply present your movie player view controller as a
modal controller on another view controller using the presentMovie
PlayerViewControllerAnimated: instance method of UIViewController.
In this recipe, we will use MPMoviePlayerController instead of MPMovie
PlayerViewController in order to get full access to various settings that
a movie player view controller does not offer, such as windowed-mode
video playback (not full-screen).
Discussion
The Media Player framework in the iOS SDK allows programmers to play audio and video files, among other interesting things To be able to play a video file, we will instantiate an object of type MPMoviePlayerController like so:
self.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:url];
In this code, moviePlayer is a property of type MPMoviePlayerController defined and synthesized for the current view controller In older iOS SDKs, programmers had very little control over how movies were played using the Media Player framework With the introduction of the iPad, the whole framework changed drastically to give more control to programmers and allow them to present their contents with more flexibility than before.
An instance of MPMoviePlayerController has a property called view This view is of type UIView and is the view in which the media, such as video, will be played As a pro- grammer, you are responsible for inserting this view into your application’s view hier- archy to present your users with the content being played Since you get a reference to
an object of type UIView, you can shape this view however you want For instance, you can simply change the background color of this view to a custom color.
Many multimedia operations depend on the notification system For instance, MPMovie PlayerController does not work with delegates; instead, it relies on notifications This allows for a very flexible decoupling between the system libraries and the applications that iOS programmers write For classes such as MPMoviePlayerController, we start
Trang 14listening for notifications that get sent by instances of that class We use the default notification center and add ourselves as an observer for a notification.
To be able to test our recipe, we need a sample mov file to play with the movie player.
You can download an Apple-provided sample file from http://support.apple.com/kb/ HT1425 Make sure you download the H.264 file format If this file is zipped, unzip it and rename it to Sample.m4v Now drag and drop this file into your application bundle
in Xcode.
After doing this, we can go ahead and write a simple program that attempts to play the
video file for us Here is our h file:
#import <UIKit/UIKit.h>
#import <MediaPlayer/MediaPlayer.h>
@interface Playing_Video_FilesViewController : UIViewController
@property (nonatomic, strong) MPMoviePlayerController *moviePlayer;
@property (nonatomic, strong) UIButton *playButton;
/* First let's construct the URL of the file in our application bundle
that needs to get played by the movie player */
NSBundle *mainBundle = [NSBundle mainBundle];
/* If we have already created a movie player before,
let's try to stop it */
if (self.moviePlayer != nil){
[self stopPlayingVideo:nil];
}
/* Now create a new movie player using the URL */
self.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:url];
if (self.moviePlayer != nil){
/* Listen for the notification that the movie player sends us
whenever it finishes playing an audio file */
Trang 15NSLog(@"Successfully instantiated the movie player.");
Please be extra careful when using the superview property of your movie
player’s view In cases such as when your view is unloaded because of
low-memory warnings, you must not use the view property of your view
controller in any way after it is set to nil Doing so will cause the iOS
SDK to throw out warnings in the console window.
Here is our implementation of the viewDidUnload method:
- (void) viewDidUnload{
self.playButton = nil;
[self stopPlayingVideo:nil];
Trang 16- (void) videoHasFinishedPlaying:(NSNotification *)paramNotification{
Trang 17/* Capture the frame at the third second into the movie */
NSNumber *thirdSecondThumbnail = [NSNumber numberWithFloat:3.0f];
/* We can ask to capture as many frames as we
want But for now, we are just asking to capture one frame */
cur-to focus on asynchronous image capture for this class.
We can use the requestThumbnailImagesAtTimes:timeOption: instance method of MPMoviePlayerController to asynchronously access thumbnails When I say “asyn- chronously,” I mean that during the time the thumbnail is being captured and reported
to your designated object (as we will soon see), the movie player will continue its work and will not block the playback We must observe the MPMoviePlayerThumbnailImage RequestDidFinishNotification notification message the movie player sends to the de- fault notification center in order to find out when our thumbnails are available:
- (void) startPlayingVideo:(id)paramSender{
/* First let's construct the URL of the file in our application bundle
that needs to get played by the movie player */
NSBundle *mainBundle = [NSBundle mainBundle];
NSString *urlAsString = [mainBundle pathForResource:@"Sample"
Trang 18ofType:@"m4v"];
NSURL *url = [NSURL fileURLWithPath:urlAsString];
/* If we have already created a movie player before,
let's try to stop it */
if (self.moviePlayer != nil){
[self stopPlayingVideo:nil];
}
/* Now create a new movie player using the URL */
self.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:url];
if (self.moviePlayer != nil){
/* Listen for the notification that the movie player sends us
whenever it finishes playing an audio file */
/* Capture the frame at the third second into the movie */
NSNumber *thirdSecondThumbnail = [NSNumber numberWithFloat:3.0f];
/* We can ask to capture as many frames as we
want But for now, we are just asking to capture one frame */
Trang 19- (void) videoThumbnailIsAvailable:(NSNotification *)paramNotification{
Trang 20is less exact, but also uses fewer system resources and offers an overall better performance when capturing thumbnails from a video MPMovieTimeOptionNearestKey Frame is usually adequate in terms of precision because it is just a couple of frames off.
9.8 Accessing the Music Library
Problem
You want to access an item that your user picks from her music library.
Solution
Use the MPMediaPickerController class:
MPMediaPickerController *mediaPicker = [[MPMediaPickerController alloc]
initWithMediaTypes:MPMediaTypeAny];
Discussion
MPMediaPickerController is a view controller that the iPod application displays to the user By instantiating MPMediaPickerController, you can present a standard view con- troller to your users to allow them to select whatever item they want from their library and then transfer the control to your application This is particularly useful in games, for instance, where the user plays the game and can have your application play his favorite tracks in the background.
You can get information from the media picker controller by becoming its delegate (conforming to MPMediaPickerControllerDelegate):
Trang 21spec (void) mediaPicker:(MPMediaPickerController *)mediaPicker
NSLog(@"Item URL = %@", itemURL);
NSLog(@"Item Title = %@", itemTitle);
NSLog(@"Item Artist = %@", itemArtist);
NSLog(@"Item Artwork = %@", itemArtwork);
Trang 22You can access different properties of each selected item using the valueForProperty: instance method of MPMediaItem Instances of this class will be returned to your application through the mediaItemCollection parameter of the mediaPicker:didPick MediaItems: delegate message.
Now let’s write a program with a very simple GUI that allows us to ask the user to pick one music item from his iPod library After he picks the music file, we will attempt to play it using an MPMusicPlayerController instance Our GUI has two simple buttons: Pick and Play, and Stop Playing The first button will ask the user to pick an item from his iPod library for us to play, and the second button will stop the audio playback (if
we are already playing the song) We will start with the design of the UI of our cation Let’s create it in a simple way, as shown in Figure 9-1
Trang 23appli-Figure 9-1 A very simple UI for our media picker and AV Audio Player
Now let's go ahead and define these two buttons in the h of our view controller:
@interface Accessing_the_Music_LibraryViewController : UIViewController
<MPMediaPickerControllerDelegate, AVAudioPlayerDelegate>
@property (nonatomic, strong) MPMusicPlayerController *myMusicPlayer;
@property (nonatomic, strong) UIButton *buttonPickAndPlay;
@property (nonatomic, strong) UIButton *buttonStopPlaying;
@end
Trang 24When our view loads up, we will then instantiate these two buttons and place them on our view:
Trang 25mes (void) mediaPicker:(MPMediaPickerController *)mediaPicker
Trang 26name:MPMusicPlayerControllerPlaybackStateDidChangeNotification
object:self.myMusicPlayer];
/* Get notified when the playback moves from one item
to the other In this recipe, we are only going to allow
our user to pick one music file */
/* And also get notified when the volume of the
music player is changed */
/* The media picker was cancelled */
NSLog(@"Media Picker was cancelled");
- (void) musicPlayerStateChanged:(NSNotification *)paramNotification{
Trang 27/* Here the media player has stopped playing the queue */
break;
}
case MPMusicPlaybackStatePlaying:{
/* The media player is playing the queue Perhaps you
can reduce some processing that your application
that is using to give more processing power
to the media player */
break;
}
case MPMusicPlaybackStatePaused:{
/* The media playback is paused here You might want
to indicate by showing graphics to the user */
We will also implement the viewDidUnload method of our view controller to make sure
we won’t leave any memory leaking:
- (void) viewDidUnload{
[super viewDidUnload];
[self stopPlayingAudio];
Trang 28self.myMusicPlayer = nil;
}
By running our application and pressing the Pick and Play button on our view troller, we will be presented with the media picker controller Once the picker view controller is displayed, the same iPod UI will be presented to our user After the user picks an item (or cancels the whole dialog), we will get appropriate delegate messages called in our view controller (since our view controller is the delegate of our media picker) After the items are picked (we only allow one item in this recipe, though), we will start our music player and start playing the whole collection.
con-If you want to allow your users to pick more than one item at a time, simply set the allowsPickingMultipleItems property of your media picker controller to YES:
mediaPicker.allowsPickingMultipleItems = YES;
Sometimes when working with the media picker controller (MPMedia
PickerController), the “MPMediaPicker: Lost connection to iPod
li-brary” message will be printed out to the console screen This is because
the media picker has been interrupted by an event such as syncing with
iTunes while the picker was being displayed to the user Immediately,
your mediaPickerDidCancel: delegate message will be called as well.
Trang 30CHAPTER 10 Address Book
10.0 Introduction
On an iOS device, the Contacts application allows users to add to, remove from, and manipulate their address book The address book can be a collection of people and groups Each person can have properties such as first name, last name, phone number, and email address assigned to her Some properties can have a single value and some can have multiple values For instance, the first name of a person is one value but the phone number can be multiple values (e.g., if the user has two home phone numbers) The AddressBook.framework framework in the iOS SDK allows us to interact with the address book database on the device We can get the array of all entities in the user’s address book, insert and change values, and much more.
To use the address-book-related functions in your application, follow these steps to first add AddressBook.framework to your application:
1 Click on your project's icon in Xcode.
2 Select the target to which you want to add the AddressBook framework.
3 On the top of the screen, select the Build Phases tab.
4 In the Build Phases tab, find and expand the Link Binary with Libraries box and press the + button, located at the bottom left corner of that box.
5 In the list that gets displayed, select the AddressBook.framework and press the Add button (see Figure 10-1 ).
Trang 31Figure 10-1 Adding the AddressBook framework to our app
After you’ve added the framework to your application, whenever you want to use address-book-related functions you must include the main header file of the framework
in your header (.h) or implementation (.m) file, like so:
#import <UIKit/UIKit.h>
#import <AddressBook/AddressBook.h>
@interface RootViewController : UIViewController
@end
You can use the Address Book framework on iOS Simulator, but the
Contacts database on the simulator is empty by default If you want to
run the examples in this chapter on iOS Simulator, first populate your
address book (on the simulator) using the Contacts application.
I have populated my iOS Simulator’s contacts database with three entries, as shown in Figure 10-2
Trang 32Figure 10-2 Contacts added to iOS Simulator
I also suggest that you populate the address book of your iOS Simulator with as many values as possible: multiple phone numbers for work and home, different addresses, and so forth Only through such diversity can you correctly test the Address Book framework’s functions.
Trang 3310.1 Getting a Reference to Address Book
Problem
You would like to get a reference to the user’s address book database.
Solution
Use the ABAddressBookCreate function in the Address Book framework:
- (BOOL) application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
ABAddressBookRef addressBook = ABAddressBookCreate();
/* Let's see if we have made any changes to the
address book or not, before attempting to save it */
if (ABAddressBookHasUnsavedChanges(addressBook)){
/* Now decide if you want to save the changes to
the address book */
NSLog(@"Changes were found in the address book.");
BOOL doYouWantToSaveChanges = YES;
/* We can make a decision to save or revert the
address book back to how it was before */
/* We failed to save the changes You can now
access the [saveError] variable to find out
what the error is */
}
} else {
/* We did NOT want to save the changes to the address
book so let's revert it to how it was before */
Trang 34/* We have not made any changes to the address book */
NSLog(@"No changes to the address book.");
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
I created the doYouWantToSaveChanges local variable and set it to YES just
to demonstrate that we can, if we need to, revert an address book whose
contents have been changed (reversion is done through the ABAddress
BookRevert procedure) You can add code, for instance, asking the user
if he wants the changes to be saved or not, and if not, you can revert the
address book to its original state.
For more information about importing the Address Book framework into your cation, please refer to this chapter’s Introduction
appli-Discussion
To get a reference to the user’s address book database, we must use the ABAddressBook Create function This function returns a value of type ABAddressBookRef that will be nil if the address book cannot be accessed You must check for nil values before accessing the address book reference returned by this function Attempting to modify
a nil address book will terminate your application with a runtime error.
After retrieving a reference to the user’s address book, you can start making changes
to the contacts, reading the entries, and so on If you have made any changes to the address book, the ABAddressBookHasUnsavedChanges function will tell you by returning the value YES.
An instance of the address book database returned by the ABAddress
BookCreate function must be released when you are finished working
with it, using the CFRelease Core Foundation method, as demonstrated
in our example code.
Trang 35After determining whether changes were made to the address book database, you can either save or discard these changes using the ABAddressBookSave or ABAddressBook Revert procedure, respectively.
10.2 Retrieving All the People in the Address Book
Problem
You want to retrieve all contacts in the user’s address book.
Solution
Use the ABAddressBookCopyArrayOfAllPeople function to retrieve an array of all contacts:
- (BOOL) application:(UIApplication *)application
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
Trang 36After accessing the user’s address book database, we can call the ABAddressBookCopy ArrayOfAllPeople function to retrieve an array of all the contacts in that address book The return value of this function is an immutable array of type CFArrayRef We cannot work with this type of array the way we work with instances of NSArray, but here are a few functions that will let us traverse an array of type CFArrayRef:
The items that are put in an array of all people, retrieved by calling the ABAddressBook CopyArrayOfAllPeople function, are of type ABRecordRef In Recipe 10.3 , you will see how to access different properties of the entries, such as a person’s entry, in the address book database.
The records in the address book database are of type ABRecordRef Each record could
be either a group or a person We have not discussed groups yet, so let’s focus on people.
Trang 37Each person could have various types of information assigned to him, such as his first name, last name, email address, and so on Bear in mind that many of these values are optional, and at the time of creating a new contact in the address book database, the user can simply leave out fields such as phone number, middle name, email address, URL, and so forth.
ABRecordCopyValue accepts an address book record and the property that has to be retrieved as its two parameters The second parameter is the property of the record that
we want to retrieve Here are some of the common properties (all of these properties
are defined as constant values in the ABPerson.h header file):
prop-kABPersonMiddleNameProperty
This value will retrieve the middle name of the given person Like the first name and the last name, the return value will be of type CFString, which can be cast to NSString with a bridge cast.
straight-email, work straight-email, and so on These values are called multivalues in the Address Book
framework Various functions allow us to work with multiple values (which are of type ABMultiValueRef):
ABMultiValueGetCount
Returns the number of value/label pairs that are inside the multivalue.
ABMultiValueCopyLabelAtIndex
Returns the label associated with a multivalue item at a specific index For instance,
if the user has three emails, such as work, home, and test emails, the index of the work email in the email multivalue would be 1 This function will then retrieve
the label associated with that email (in this example, work) Please bear in mind
that multivalues do not necessarily have to have labels Make sure you check for NULL values.
Trang 38Returns the string value associated with a multivalue item at a specific index I know this sounds complicated, but imagine the user has work, home, and test emails If we provide the index 0 to this function, it will retrieve the given contact’s work email.
Now let's first go ahead and write a simple app that can retrieve all the people in the address book and print out their first name, last name and email address objects:
- (BOOL) application:(UIApplication *)application
NSLog(@"First Name = %@", firstName);
NSLog(@"Last Name = %@", lastName);
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];
Trang 39If I run this app, with the 3 contacts that I've added to my Address Book, I will get the following printed to the console window:
Successfully accessed the address book
First Name = Vandad
Last Name = Nahavandipoor
Address = ABMultiValueRef 0x684ad50 with 2 value(s)
0: _$!<Home>!$_ (0x684af00) - vandad.np@gmail.com (0x684af20)
1: _$!<Work>!$_ (0x684aee0) - iosandosx@gmail.com (0x684af90)
First Name = Brian
Last Name = Jepson
Address = ABMultiValueRef 0x684ac00 with 1 value(s)
0: _$!<Home>!$_ (0x684adf0) - brian@oreilly.com (0x684ae10)
First Name = Andy
Last Name = Oram
Address = ABMultiValueRef 0x684a710 with 1 value(s)
0: _$!<Home>!$_ (0x684ace0) - andy@oreilly.com (0x684ad00)
It's immediately visible that the multi-value field, email, cannot be read as a plain string object So let's go ahead, using the functions that we just learnt, implement a method that accepts an object of type ABRecordRef and correctly reads that record's multi-value email field and prints the values out to the console:
/* Get the label of the email (if any) */
NSString *emailLabel = ( bridge_transfer NSString *)
ABMultiValueCopyLabelAtIndex(emails, emailCounter);
NSString *localizedEmailLabel = ( bridge_transfer NSString *)
ABAddressBookCopyLocalizedLabel(( bridge CFStringRef)emailLabel);
/* And then get the email address itself */
NSString *email = ( bridge_transfer NSString *)
ABMultiValueCopyValueAtIndex(emails, emailCounter);
Trang 40NSLog(@"First Name = %@", firstName);
NSLog(@"Last Name = %@", lastName);
self.window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];