Using the iPod Library

Your application may need more control over managing and choosing media items than you get with the media item picker. If you want to provide a custom user interface to the device iPod library, perform specific queries, or associate custom metadata with media items, you need the database access classes of this API.

Figure 4-1 illustrates how your application and the database access classes interact to retrieve media items.

Figure 4-1  Using the iPod library database access classes

First, take a moment to notice the similarity between this figure and Figure 1-5. The earlier figure helped explain what a query is. This figure places the media query class among its cohorts, showing how all the database access classes relate to each other.

The figure illustrates two scenarios of interaction between your application and the device iPod library. First, moving counterclockwise from the “Your application” icon, the figure depicts the creation and configuration of a query that in turn defines a media item collection. The collection points to a particular subset of items from the library. Although not shown in the figure, the collection is the return value of invoking the query. Each media item owns a media item artwork object, as shown in the figure, among its other properties.

The second scenario in Figure 4-1 is your application receiving change notifications from the iPod library by way of the MPMediaLibrary class. By registering to receive these change notifications, your application can update any cache of library content if a user syncs their device while your application is running.

Retrieving Ungrouped Media Items

Retrieving media items from the device iPod library begins with constructing a media query. The simplest query is the generic “everything” query shown in Listing 4-1, which matches the entire contents of the library. The example then logs the titles of the media items to the Xcode debugger console, demonstrating use of the items property to invoke the query and retrieve the media items it matches.

Listing 4-1  Creating and using a generic media query

MPMediaQuery *everything = [[MPMediaQuery alloc] init];
 
NSLog(@"Logging items from a generic query...");
NSArray *itemsFromGenericQuery = [everything items];
for (MPMediaItem *song in itemsFromGenericQuery) {
    NSString *songTitle = [song valueForProperty: MPMediaItemPropertyTitle];
    NSLog (@"%@", songTitle);
}

To construct a more specific query, apply one or more media property predicates to a generic query. A predicate specifies a single logical condition to test media items against.

Listing 4-2 creates a predicate containing the condition that a media item’s “artist” property must have a particular value. The listing then adds the predicate to a query.

Listing 4-2  Constructing and applying a media property predicate

MPMediaPropertyPredicate *artistNamePredicate =
    [MPMediaPropertyPredicate predicateWithValue: @"Happy the Clown"
                                     forProperty: MPMediaItemPropertyArtist];
 
MPMediaQuery *myArtistQuery = [[MPMediaQuery alloc] init];
[myArtistQuery addFilterPredicate: artistNamePredicate];
 
NSArray *itemsFromArtistQuery = [myArtistQuery items];

If you were to run this code, the itemsFromArtistQuery array would contain only those items from the iPod library that are by Happy the Clown.

You can construct and add multiple predicates to a query to narrow what the query matches. Listing 4-3 shows this technique using two predicates.

Listing 4-3  Applying multiple predicates to an existing media query

MPMediaPropertyPredicate *artistNamePredicate =
    [MPMediaPropertyPredicate predicateWithValue: @"Sad the Joker"
                                     forProperty: MPMediaItemPropertyArtist];
 
MPMediaPropertyPredicate *albumNamePredicate =
    [MPMediaPropertyPredicate predicateWithValue: @"Stair Tumbling"
                                     forProperty: MPMediaItemPropertyAlbumTitle];
 
MPMediaQuery *myComplexQuery = [[MPMediaQuery alloc] init];
 
[myComplexQuery addFilterPredicate: artistNamePredicate];
[myComplexQuery addFilterPredicate: albumNamePredicate];

You construct each predicate using a value of your choice (such as an artist’s name) along with the appropriate media item property key. These keys are described in MPMediaItem Class Reference in General Media Item Property Keys and Podcast Item Property Keys.

When you apply more than one predicate to a query, the query combines them using the logical AND operator.

You can also add predicates to a query upon initialization, as shown in Listing 4-4. (The two predicates in this example are assumed to be previously defined.)

Listing 4-4  Applying multiple predicates when initializing a media query

NSSet *predicates =
    [NSSet setWithObjects: artistNamePredicate, albumNamePredicate, nil];
 
MPMediaQuery *specificQuery =
    [[MPMediaQuery alloc] initWithFilterPredicates: predicates];

Listing 4-4 first defines an NSSet object that contains two predicates, then allocates and initializes a media query using the initWithFilterPredicates: class method.

Only certain property keys can be used to build valid predicates. These keys are tagged as “filterable” in MPMediaItem Class Reference. If you attempt to use a query that contains an invalid predicate, the resulting behavior is undefined.

Listing 4-5 shows how to use the canFilterByProperty: class method to validate a media item property key before using it in a predicate.

Listing 4-5  Testing if a property key can be used for a media property predicate

if ([MPMediaItem canFilterByProperty: MPMediaItemPropertyGenre]) {
 
    MPMediaPropertyPredicate *rockPredicate =
        [MPMediaPropertyPredicate predicateWithValue: @"Rock"
                                         forProperty: MPMediaItemPropertyGenre];
 
    [query addFilterPredicate: rockPredicate];
}

In Listing 4-5, only if the indicated media item property key (in this example, MPMediaItemPropertyGenre) can be used to construct a valid predicate will the body of the if statement execute. Apple recommends that you always perform a check like this before constructing a media property predicate.

Retrieving Media Item Collections

A media query is useful not only for retrieving ungrouped media items. You can also use a media query to retrieve sorted and arranged collections of media items. The arrangement you get depends on the value you set for the media query’s grouping property.

Listing 4-6 shows how to retrieve all the songs by a particular artist, with those songs arranged into albums. The example logs the results to the Xcode debugger console.

Listing 4-6  Using grouping type to specify media item collections

MPMediaQuery *query = [[MPMediaQuery alloc] init];
 
[query addFilterPredicate: [MPMediaPropertyPredicate
                               predicateWithValue: @"Moribund the Squirrel"
                                      forProperty: MPMediaItemPropertyArtist]];
// Sets the grouping type for the media query
[query setGroupingType: MPMediaGroupingAlbum];
 
NSArray *albums = [query collections];
for (MPMediaItemCollection *album in albums) {
    MPMediaItem *representativeItem = [album representativeItem];
    NSString *artistName =
        [representativeItem valueForProperty: MPMediaItemPropertyArtist];
    NSString *albumName =
        [representativeItem valueForProperty: MPMediaItemPropertyAlbumTitle];
    NSLog (@"%@ by %@", albumName, artistName);
 
    NSArray *songs = [album items];
    for (MPMediaItem *song in songs) {
        NSString *songTitle =
            [song valueForProperty: MPMediaItemPropertyTitle];
        NSLog (@"\t\t%@", songTitle);
    }
}

You can see in Listing 4-6 that the value of the media query’s collections property is an array of arrays. The outer for loop iterates over the albums performed the specified artist. The inner for loop iterates over the songs in the current album.

The MPMediaQuery class includes several convenience constructors for creating queries that are preconfigured with a grouping type. The following statement, for example, attaches the “albums” grouping type to a newly-allocated query:

MPMediaQuery *query = [MPMediaQuery albumsQuery];

For descriptions of all the convenience constructors, see MPMediaQuery Class Reference.

Using Media Item Artwork

One of the most useful and high-impact properties of a media item is its artwork. To display artwork, you use Interface Builder as well as Xcode. The steps are as follows:

  1. Add a UIImageView object to your view layout in Interface Builder.

  2. Add an IBOutlet instance variable to your view controller class to connect to the UIImageView object.

  3. Retrieve the artwork from the media item that owns it (having previously retrieved the media item as described in this chapter).

  4. Convert the artwork to a UIImage object, then assign it to your layout’s UIImageView object.

Listing 4-7 shows how to do steps 3 and 4.

Listing 4-7  Displaying album artwork for a media item

MPMediaItemArtwork *artwork =
    [mediaItem valueForProperty: MPMediaItemPropertyArtwork];
UIImage *artworkImage =
    [artwork imageWithSize: albumImageView.bounds.size];
 
if (artworkImage) {
    albumImageView.image = artworkImage;
} else {
    albumImageView.image = [UIImage imageNamed: @"noArtwork.png"];
}

The noArtwork.png file used in the last line of Listing 4-7 is a fallback image that you add to your Xcode project, for use when a media item has no associated artwork.