As a courtesy, this is a full free rendering of my book, Programming iOS 6, by Matt Neuburg. Copyright 2013 Matt Neuburg. Please note that this edition is outdated; the current books are iOS 13 Programming Fundamentals with Swift and Programming iOS 13. If my work has been of help to you, please consider purchasing one or both of them, or you can reward me through PayPal at http://www.paypal.me/mattneub. Thank you!

Chapter 29. Music Library

An iOS device can be used for the same purpose as the original iPod — to hold and play music and podcasts. These items constitute the device’s music library; the user can play them with the Music app (formerly called the iPod app on some devices). iOS provides the programmer with various forms of access to the device’s music library; you can:

These abilities are provided by the Media Player framework. You’ll need to link to MediaPlayer.framework and import <MediaPlayer/MediaPlayer.h>.

Exploring the Music Library

Everything in the music library, as seen by your code, is an MPMediaEntity. This is an abstract class that endows its subclasses with the ability to describe themselves through key–value pairs called properties. (This use of the word “properties” has nothing to do with the Objective-C properties discussed in Chapter 12; these properties are more like entries in an NSDictionary.) The repertoire of properties depends on the sort of entity you’re looking at; many of them will be intuitively familiar from your use of iTunes. For example, a media item has a title, an album title, a track number, an artist, a composer, and so on; a playlist has a title, a flag indicating whether it is a “smart” playlist, and so on. The property keys have names like MPMediaItemPropertyTitle.

To fetch a property’s value, call valueForProperty: with its key. You can fetch multiple properties with enumerateValuesForProperties:usingBlock:.

An individual item in the music library is an MPMediaItem, an MPMediaEntity subclass. It has a type, according to the value of its MPMediaItemPropertyMediaType property: it might, for example, be music, a podcast, an audiobook, or a video. Different types of item have slightly different properties; for example, a podcast, in addition to its normal title, has a podcast title.

An item’s artwork image is an instance of the MPMediaItemArtwork class, from which you are supposed to be able to get the image itself scaled to a specified size by calling imageWithSize:; my experience is that in reality you’ll receive an image of any old size the system cares to give you, so you may have to scale it further yourself. This, for example, is what my Albumen app does:

MPMediaItemArtwork* art = //...
UIImage* im = [art imageWithSize:CGSizeMake(36,36)];
// but it probably *isn't* 36 by 36; scale it so that it is
if (im) {
    CGFloat scalew = 36.0/im.size.width;
    CGFloat scaleh = 36.0/im.size.height;
    CGFloat scale = (scalew < scaleh) ? scalew : scaleh;
    CGSize sz = CGSizeMake(im.size.width*scale, im.size.height*scale);
    UIGraphicsBeginImageContextWithOptions(sz, NO, 0);
    [im drawInRect:CGRectMake(0,0,sz.width,sz.height)];
    im = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
}

A playlist is an MPMediaPlaylist. As you would expect, it has items and a count of those items. It inherits those properties from its superclass, MPMediaItemCollection, which is the other MPMediaEntity subclass. I’ll talk more about MPMediaItemCollection in a moment.

Obtaining actual information from the music library requires a query, an MPMediaQuery. First, you form the query. There are two main ways to do this:

With a convenience constructor

MPMediaQuery provides several class methods that form a query ready to ask the music library for all of its songs, or all of its podcasts, and so on. Here’s the complete list:

  • songsQuery
  • podcastsQuery
  • audiobooksQuery
  • playlistsQuery
  • albumsQuery
  • artistsQuery
  • composersQuery
  • genresQuery
  • compilationsQuery
With filter predicates

You can attach to the query one or more MPMediaPropertyPredicate instances, forming a set (NSSet) of predicates. These predicates filter the music library according to criteria you specify; to be included in the result, a media item must successfully pass through all the filters (in other words, the predicates are combined using logical-and). A predicate is a simple comparison. It has two, or possibly three, aspects:

A property
The key to the property you want to compare against. Not every property can be used in a filter predicate; the documentation makes the distinction clear (and you can get additional help from an MPMediaEntity class method, canFilterByProperty:).
A value
The value that the specified property must have in order to pass through the filter.
A comparison type (optional)
In order to pass through the filter, a media item’s property value can either match the value you provide (MPMediaPredicateComparisonEqualTo, the default) or contain the value you provide (MPMediaPredicateComparisonContains).

These two ways of forming a query are actually the same; a convenience constructor is just a quick way of obtaining a query already endowed with a filter predicate.

A query also groups its results, according to its groupingType. Your choices are:

  • MPMediaGroupingTitle
  • MPMediaGroupingAlbum
  • MPMediaGroupingArtist
  • MPMediaGroupingAlbumArtist
  • MPMediaGroupingComposer
  • MPMediaGroupingGenre
  • MPMediaGroupingPlaylist
  • MPMediaGroupingPodcastTitle

The query convenience constructors all supply a groupingType in addition to a filter predicate. Indeed, the grouping is often the salient aspect of the query. For example, an albumsQuery is in fact merely a songsQuery with the added feature that its results are grouped by album.

The groups resulting from a query are collections; that is, each is an MPMediaItemCollection. This class, you will recall, is the superclass of MPMediaPlaylist, and is an MPMediaEntity subclass. So, a collection has properties; it also has items and a count. It also has a representativeItem property, which gives you just one item from the collection. The reason you need this is that properties of a collection are often embodied in its items rather than in the collection itself. For example, an album has no title; rather, its items have album titles that are all the same. So to learn the title of an album, you ask for the album title of a representative item.

Warning

Unfortunately, in iOS 6, asking for a representativeItem can result in some nasty-looking log messages: “Attempting a write transaction on a read-only database,” and “BEGIN IMMEDIATE could unexpectedly not be stepped.” A possible workaround is to use items[0] instead of representativeItem (even though they are not quite the same thing).

After you form the query, you perform the query. You do this simply by asking for the query’s results. You can ask either for its collections (if you care about the groups returned from the query) or for its items. Here, I’ll discover the titles of all the albums:

MPMediaQuery* query = [MPMediaQuery albumsQuery];
NSArray* result = [query collections];
// prove we've performed the query, by logging the album titles
for (MPMediaItemCollection* album in result)
    NSLog(@"%@", [album.representativeItem // or album.items[0]
        valueForProperty:MPMediaItemPropertyAlbumTitle]);
/*
Output starts like this on my device:
Beethoven Concertos
Beethoven Overtures Etc
Beethoven Piano Duet
Beethoven Piano Other
Beethoven Piano Sonatas
...
*/

Now let’s make our query more elaborate; we’ll get the titles of all the albums whose name contains “Sonata”. Observe that what we really do is to ask for all songs whose album title contains “Sonata”, grouped by album:

MPMediaQuery* query = [MPMediaQuery albumsQuery];
MPMediaPropertyPredicate* hasSonata =
    [MPMediaPropertyPredicate predicateWithValue:@"Sonata"
        forProperty:MPMediaItemPropertyAlbumTitle
     comparisonType:MPMediaPredicateComparisonContains];
[query addFilterPredicate:hasSonata];
NSArray* result = [query collections];
for (MPMediaItemCollection* album in result)
    NSLog(@"%@", [album.representativeItem // or album.items[0]
        valueForProperty:MPMediaItemPropertyAlbumTitle]);
/*
Output starts like this on my device:
Beethoven Piano Sonatas
Beethoven Violin Sonatas
Schubert Piano Sonatas
Brahms Sonatas
Mozart Church Sonatas
...
*/

Because the results of that query are actually songs (MPMediaItems), we can immediately access any song in any of those albums. Let’s modify the output from our previous query to print the titles of all the songs in the first album returned, which happens to be the Beethoven Piano Sonatas album. We don’t have to change our query, so I’ll start at the point where we perform it:

// ... same as before ...
NSArray* result = [query collections];
MPMediaItemCollection* album = result[0];
for (MPMediaItem* song in album.items)
    NSLog(@"%@", [song valueForProperty:MPMediaItemPropertyTitle]);
/*
Output starts like this on my device:
Piano Sonata #1 In F Minor, Op. 2/1 - 1. Allegro
Piano Sonata #1 In F Minor, Op. 2/1 - 2. Adagio
Piano Sonata #1 In F Minor, Op. 2/1 - 3. Menuetto: Allegretto
Piano Sonata #1 In F Minor, Op. 2/1 - 4. Prestissimo
Piano Sonata #2 In A Minor, Op. 2/2 - 1. Allegro Vivace
...
*/

One of the properties of an MPMediaEntity is its persistent ID, which uniquely identifies this song (MPMediaItemPropertyPersistentID) or playlist (MPMediaPlaylistPropertyPersistentID). No other means of identification is guaranteed unique; two songs or two playlists can have the same title, for example. Using the persistent ID, you can retrieve again at a later time the same song or playlist you retrieved earlier, even across launches of your app. All sorts of things have persistent IDs — entities in general (MPMediaEntityPropertyPersistentID), albums, artists, composers, and more.

While you are maintaining the results of a search, the contents of the music library may themselves change. For example, the user might connect the device to a computer and add or delete music with iTunes. This can put your results out of date. For this reason, the library’s own modified state is available through the MPMediaLibrary class. Call the class method defaultMediaLibrary to get the actual library instance; now you can ask it for its lastModifiedDate. You can also register to receive a notification, MPMediaLibraryDidChangeNotification, when the music library is modified; this notification is not emitted unless you first send the library beginGeneratingLibraryChangeNotifications. You should eventually balance this with endGeneratingLibraryChangeNotifications.

New in iOS 6, a song has a property MPMediaItemPropertyIsCloudItem, allowing you to ask whether it lives in the cloud (thanks to iTunes Match) or on the device. The distinction is clearer in than it was in iOS 5, because a song can now be played from the cloud without downloading it, and the user can manually download a song from the cloud or delete it from the device. Such changes in a song’s cloud status do not count as a change in the library.

The Music Player

The Media Player framework class for playing an MPMediaItem is MPMusicPlayerController. It comes in two flavors, depending on which class method you use to get an instance:

applicationMusicPlayer
Plays an MPMediaItem from the music library within your application. The song being played by the applicationMusicPlayer can be different from the Music app’s current song. This player stops when your app is not in the foreground.
iPodMusicPlayer
The global music player — the very same player used by the Music app. This might already be playing an item, or might be paused with a current item, at any time while your app runs; you can learn or change what item this is. The global music player continues playing independently of the state of your app, and the user can at any time alter what it is doing.

Warning

An applicationMusicPlayer is not really inside your app. It is actually the global music player behaving differently. It has its own audio session. You cannot play its audio when your app is in the background. You cannot make it the target of remote control events. If these limitations prove troublesome, use the iPodMusicPlayer (or AVPlayer, discussed later in this chapter).

A music player doesn’t merely play an item; it plays from a queue of items. This behavior is familiar from iTunes and the Music app. For example, in iTunes, when you switch to a playlist and double-click the first song to start playing, when iTunes comes to the end of that song, it proceeds by default to the next song in the playlist. So at that moment, its queue is the totality of songs in the playlist. The music player behaves the same way; when it reaches the end of a song, it proceeds to the next song in its queue.

Your methods for controlling playback also reflect this queue-based orientation. In addition to the expected play, pause, and stop commands, there’s a skipToNextItem and skipToPreviousItem command. Anyone who has ever used iTunes or the Music app (or, for that matter, an old-fashioned iPod) will have an intuitive grasp of this and everything else a music player does. For example, you can also set a music player’s repeatMode and shuffleMode, just as in iTunes.

You provide a music player with its queue in one of two ways:

With a query
You hand the music player an MPMediaQuery. The query’s items are the items of the queue.
With a collection
You hand the music player an MPMediaItemCollection. This might be obtained from a query you performed, but you can also assemble your own collection of MPMediaItems in any way you like, putting them into an array and calling collectionWithItems: or initWithItems:.

In this example, we collect all songs in the library shorter than 30 seconds into a queue and set the queue playing in random order using the application-internal music player:

MPMediaQuery* query = [MPMediaQuery songsQuery];
NSMutableArray* marr = [NSMutableArray array];
MPMediaItemCollection* queue = nil;
for (MPMediaItem* song in query.items) {
    NSNumber* dur =
        [song valueForProperty:MPMediaItemPropertyPlaybackDuration];
    if ([dur floatValue] < 30)
        [marr addObject: song];
}
if ([marr count] == 0)
    NSLog(@"No songs that short!");
else
    queue = [MPMediaItemCollection collectionWithItems:marr];
if (queue) {
    MPMusicPlayerController* player =
        [MPMusicPlayerController applicationMusicPlayer];
    [player setQueueWithItemCollection:queue];
    player.shuffleMode = MPMusicShuffleModeSongs;
    [player play];
}

If a music player is currently playing, setting its queue will stop it; restarting play is up to you.

You can ask a music player for its nowPlayingItem, and since this is an MPMediaItem, you can learn all about it through its properties. Unfortunately, you can’t query a music player as to its queue, but you can keep your own pointer to the MPMediaItemCollection constituting the queue when you hand it to the music player, and you can ask the music player for which song within the queue is currently playing (indexOfNowPlayingItem). The user can completely change the queue of an iPodMusicPlayer, so if control over the queue is important to you, use the applicationMusicPlayer.

A music player has a playbackState that you can query to learn what it’s doing (whether it is playing, paused, stopped, or seeking). It also emits notifications so you can hear about changes in its state:

  • MPMusicPlayerControllerPlaybackStateDidChangeNotification
  • MPMusicPlayerControllerNowPlayingItemDidChangeNotification
  • MPMusicPlayerControllerVolumeDidChangeNotification

These notifications are not emitted until you tell the music player to beginGeneratingPlaybackNotifications. This is an instance method, so you can arrange to receive notifications from just one particular music player if you like. If you do receive notifications from both, you can distinguish them by examining the NSNotification’s object and comparing it to each player. You should eventually balance this call with endGeneratingPlaybackNotifications.

To illustrate, I’ll extend the previous example to set a UILabel in our interface every time a different song starts playing. Before we start the player playing, we insert these lines to generate the notifications:

[player beginGeneratingPlaybackNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(changed:)
    name:MPMusicPlayerControllerNowPlayingItemDidChangeNotification
    object:player];
self.q = queue; // retain a pointer to the queue

And here’s how we respond to those notifications:

- (void) changed: (NSNotification*) n {
    MPMusicPlayerController* player =
        [MPMusicPlayerController applicationMusicPlayer];
    if ([n object] == player) { // just playing safe
        NSString* title =
            [player.nowPlayingItem valueForProperty:MPMediaItemPropertyTitle];
        NSUInteger ix = player.indexOfNowPlayingItem;
        [self->label setText: [NSString stringWithFormat:@"%i of %i: %@",
                               ix+1, [self.q count], title]];
    }
}

There’s no periodic notification as a song plays and the current playhead position advances. To get this information, you’ll have to resort to polling. This is not objectionable as long as your polling interval is reasonably sparse; your display may occasionally fall a little behind reality, but this won’t usually matter. To illustrate, let’s add to our existing example a UIProgressView (p) showing the current percentage of the current song played by the global player. There’s no notification, so I’ll use an NSTimer and poll the state of the player every 2 seconds:

self.timer = [NSTimer scheduledTimerWithTimeInterval:2
                 target:self selector:@selector(timerFired:)
                 userInfo:nil repeats:YES];

When the timer fires (timerFired:), the progress view displays the state of the currently playing item:

MPMusicPlayerController* mp =
    [MPMusicPlayerController applicationMusicPlayer];
MPMediaItem* item = mp.nowPlayingItem;
if (!item || mp.playbackState == MPMusicPlaybackStateStopped) {
    self.p.hidden = YES;
    return;
}
self.p.hidden = NO;
NSTimeInterval current = mp.currentPlaybackTime;
NSTimeInterval total =
[[item valueForProperty:MPMediaItemPropertyPlaybackDuration] doubleValue];
self.p.progress = current / total;

The applicationMusicPlayer has no user interface, unless you count the remote playback controls (Figure 27.1); if you want the user to have controls for playing and stopping a song, you’ll have to create them yourself. The iPodMusicPlayer has its own natural interface — the Music app.

The Media Player framework does offer a slider for setting the system output volume, along with an AirPlay route button if appropriate; this is an MPVolumeView. An MPVolumeView works only on a device — not in the Simulator. Starting in iOS 6, it is customizable similarly to a UISlider; you can set the images for the two halves of the track, the thumb, and even the AirPlay route button, for both the Normal and the Highlighted state (while the user is touching the thumb). A nice feature is that you can retrieve the MPVolumeView’s default images, so that you can base the modified images upon them. In this example, we make the left half of the track black and the right half red, and we make the thumb larger:

CGSize sz = CGSizeMake(20,20);
UIGraphicsBeginImageContextWithOptions(
    CGSizeMake(sz.height,sz.height), NO, 0);
[[UIColor blackColor] setFill];
[[UIBezierPath bezierPathWithOvalInRect:
    CGRectMake(0,0,sz.height,sz.height)] fill];
UIImage* im1 = UIGraphicsGetImageFromCurrentImageContext();
[[UIColor redColor] setFill];
[[UIBezierPath bezierPathWithOvalInRect:
    CGRectMake(0,0,sz.height,sz.height)] fill];
UIImage* im2 = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

[self.vv setMinimumVolumeSliderImage:
    [im1 resizableImageWithCapInsets:UIEdgeInsetsMake(9,9,9,9)
                        resizingMode:UIImageResizingModeStretch]
                            forState:UIControlStateNormal];
[self.vv setMaximumVolumeSliderImage:
    [im2 resizableImageWithCapInsets:UIEdgeInsetsMake(9,9,9,9)
                        resizingMode:UIImageResizingModeStretch]
                            forState:UIControlStateNormal];

UIImage* thumb = [self.vv volumeThumbImageForState:UIControlStateNormal];
sz = thumb.size;
sz.width +=10; sz.height += 10;
UIGraphicsBeginImageContextWithOptions(sz, NO, 0);
[thumb drawInRect:CGRectMake(0,0,sz.width,sz.height)];
UIImage* im3 = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

[self.vv setVolumeThumbImage:im3 forState:UIControlStateNormal];

MPMusicPlayerController is convenient and simple, but it’s also simple-minded. Its audio session isn’t your audio session; the music player doesn’t really belong to you. An MPMediaItem, however, has an MPMediaItemPropertyAssetURL key whose value is a URL suitable for forming an AVAsset. Thus, another way to play an MPMediaItem is through AV Foundation (Chapter 28). This approach puts playback of the song into your app’s audio session and allows you to control it in response to remote control events and to play it while your app is in the background. (Of course, you can do a lot more with AV Foundation than merely to play a song from the music library. For example, you could incorporate a song, or part of a song, as the sound track to a movie.)

In this simple example, we start with an array of MPMediaItems and initiate play of those items in an AVQueuePlayer:

NSArray* arr = // array of MPMediaItem;
NSMutableArray* assets = [NSMutableArray array];
for (MPMediaItem* item in arr) {
    AVPlayerItem* pi = [[AVPlayerItem alloc] initWithURL:
        [item valueForProperty:MPMediaItemPropertyAssetURL]];
    [assets addObject:pi];
}
self.qp = [AVQueuePlayer queuePlayerWithItems:assets];
[self.qp play];

That’s easy enough, but I have the impression, based on something said in one of the WWDC 2011 videos, that it’s not what you’re supposed to do. Instead of adding a whole batch of AVPlayerItems to an AVQueuePlayer all at once, you should add just a few AVPlayerItems to start with and then add each additional AVPlayerItem when an item finishes playing. So I’ll start out by adding just three AVPlayerItems, and use KVO to observe the AVQueuePlayer’s @"currentItem" key:

NSArray* arr = // array of MPMediaItem;
self.assets = [NSMutableArray array];
for (MPMediaItem* item in arr) {
    AVPlayerItem* pi = [[AVPlayerItem alloc] initWithURL:
        [item valueForProperty:MPMediaItemPropertyAssetURL]];
    [self.assets addObject:pi];
}
self->_curnum = 0; // we'll need this later
self->_total = [self.assets count]; // ditto
self.qp = [AVQueuePlayer queuePlayerWithItems:
    [self.assets objectsAtIndexes:
        [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,3)]]];
[self.assets removeObjectsAtIndexes:
        [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0,3)]];
[self.qp addObserver:self forKeyPath:@"currentItem" options:0 context:nil];
[self.qp play];

The implementation of observeValueForKeyPath:... looks like this:

AVPlayerItem* item = self.qp.currentItem;
NSArray* arr = item.asset.commonMetadata;
arr = [AVMetadataItem metadataItemsFromArray:arr
                                     withKey:AVMetadataCommonKeyTitle
                                    keySpace:AVMetadataKeySpaceCommon];
AVMetadataItem* met = arr[0];
[met loadValuesAsynchronouslyForKeys:@[@"value"]
                   completionHandler:^{
    dispatch_async(dispatch_get_main_queue(), ^{
        self.label.text = [NSString stringWithFormat:@"%i of %i: %@",
                            ++self->_curnum, self->_total,
                            [met valueForKey:@"value"]];
    });
}];
if (![self.assets count])
    return;
AVPlayerItem* newItem = self.assets[0];
[self.qp insertItem:newItem afterItem:[self.qp.items lastObject]];
[self.assets removeObjectAtIndex:0];

That code illustrates how to extract metadata from an AVAsset by way of an AVMetadataItem; in this case, we fetch the AVMetadataCommonKeyTitle and get its value, as the equivalent of fetching an MPMediaItem’s MPMediaItemPropertyTitle property in our earlier code. loadValuesAsynchronouslyForKeys:completionHandler: is the way to retrieve a property from various AV Foundation classes, including AVMetadataItem. There are no guarantees about what thread the completion handler will be called on, so to set the label’s text, I step out to the main thread (more about that in Chapter 38).

In the last three lines, we pull an AVPlayerItem off the front of our assets mutable array and add it to the end of the AVQueuePlayer’s queue. The AVQueuePlayer itself deletes an item from the start of its queue after playing it, so this way the queue never exceeds three items in length.

Just as in the previous example, where we updated a progress view in response to the firing of a timer to reflect an MPMusicPlayerController’s current item’s time and duration, we can do the same thing with the currently playing AVPlayerItem. Here’s the code that runs when our timer fires:

if (self.qp.rate < 0.01)
    self.p.hidden = YES;
else {
    self.p.hidden = NO;
    AVPlayerItem* item = self.qp.currentItem;
    CMTime cur = self.qp.currentTime;
    CMTime dur = item.duration;
    self.p.progress = CMTimeGetSeconds(cur)/CMTimeGetSeconds(dur);
}

The Music Picker

The music picker (MPMediaPickerController) is a view controller (UIViewController) whose view is a self-contained navigation interface in which the user can select a media item. This interface looks very much like the Music app. You have no access to the actual view; you are expected to present the view controller (or, on the iPad, to use a popover).

You can limit the type of media items displayed by creating the controller using initWithMediaTypes:. You can make a prompt appear at the top of the navigation bar (prompt). And you can govern whether the user can choose multiple media items or just one, with the allowsPickingMultipleItems property. New in iOS 6, you can filter out items stored in the cloud (through iTunes Match) by setting showsCloudItems to NO. That’s all there is to it.

While the view is showing, you learn what the user is doing through two delegate methods (MPMediaPickerControllerDelegate):

  • mediaPicker:didPickMediaItems:
  • mediaPickerDidCancel:

How you use these depends on the value of the controller’s allowsPickingMultipleItems:

The controller’s allowsPickingMultipleItems is NO (the default)
Every time the user taps a media item, your mediaPicker:didPickMediaItems: is called, handing you an MPMediaItemCollection consisting of all items the user has tapped so far (including the same item multiple times if the user taps the same item more than once). When the user taps Cancel, your mediaPickerDidCancel: is called.
The controller’s allowsPickingMultipleItems is YES
The interface has Plus buttons at the right end of every media item, similar to the Music app interface for creating a playlist. When the user taps Done, mediaPicker:didPickMediaItems: is called, handing you an MPMediaItemCollection consisting of all items for which the user has tapped the Plus button (including the same item multiple times if the user taps the same item’s Plus button more than once). Your mediaPickerDidCancel: is never called.

The view is not automatically dismissed; it is up to you to dismiss the presented view controller.

In this example, we put up the music picker, allowing the user to choose one media item; we then play that media item with the application’s music player:

- (void) presentPicker {
    MPMediaPickerController* picker = [MPMediaPickerController new];
    picker.delegate = self;
    [self presentViewController:picker animated:YES completion:nil];
}

- (void) mediaPicker: (MPMediaPickerController*) mediaPicker
        didPickMediaItems: (MPMediaItemCollection*) mediaItemCollection {
    MPMusicPlayerController* player =
        [MPMusicPlayerController applicationMusicPlayer];
    [player setQueueWithItemCollection:mediaItemCollection];
    [player play];
    [self dismissViewControllerAnimated:YES completion:nil];
}

- (void) mediaPickerDidCancel: (MPMediaPickerController*) mediaPicker {
    [self dismissViewControllerAnimated:YES completion:nil];
}

On the iPad, the music picker can be displayed as a presented view, but it also works very well in a popover. I’ll use this opportunity to provide a complete example (Example 29.1) of managing a single view controller as either a presented view or a popover. The presentPicker method is now a button’s control event action handler, so that we can point the popover’s arrow to the button. How we summon the picker depends on the device; we use UI_USER_INTERFACE_IDIOM to distinguish the two cases. If it’s an iPad, we create a popover and set an instance variable to retain it (as discussed in Chapter 22). Two methods dismiss the picker, so that operation is factored out into a utility method (dismissPicker:) that does one thing if there’s a popover and another if there’s a presented view controller.

Example 29.1. A presented view on the iPhone, a popover on the iPad

- (void) presentPicker: (id) sender {
    MPMediaPickerController* picker = [MPMediaPickerController new];
    picker.delegate = self;
    if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
        [self presentViewController:picker animated:YES completion:nil];
    else {
        UIPopoverController* pop =
            [[UIPopoverController alloc] initWithContentViewController:picker];
        self.currentPop = pop;
        [pop presentPopoverFromRect:[sender bounds] inView:sender
            permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
        pop.passthroughViews = nil;
    }
}

- (void) dismissPicker: (MPMediaPickerController*) mediaPicker {
    if (self.currentPop && self.currentPop.popoverVisible) {
        [self.currentPop dismissPopoverAnimated:YES];
    } else {
        [self dismissViewControllerAnimated:YES completion:nil];
    }
}

- (void)mediaPicker: (MPMediaPickerController *)mediaPicker
        didPickMediaItems:(MPMediaItemCollection *)mediaItemCollection {
    MPMusicPlayerController* player =
        [MPMusicPlayerController applicationMusicPlayer];
    [player setQueueWithItemCollection:mediaItemCollection];
    [player play];
    [self dismissPicker: mediaPicker];
}

- (void)mediaPickerDidCancel:(MPMediaPickerController *)mediaPicker {
    [self dismissPicker: mediaPicker];
}

- (void)popoverControllerDidDismissPopover:(UIPopoverController*)popoverController {
    self.currentPop = nil;
}