MPMediaQuery search for Artists, Albums, and Songs

2020-02-09 04:10发布

How can I search the iPod Library in the same manner as the iOS Music application? I want to make general queries that return results for each Artists, Albums, and Songs. For instance, if I search Kenny Chesney I want the songs query to return all Kenny Chesney songs (and any songs titles or albums that contain Kenny Chesney in them.) When I make this query and a predicate for each property (song title, album title, artist name), an empty array returns.

Here is a bit of code that may give you a better idea of what I am attempting to accomplish:

MPMediaPropertyPredicate *songPredicate =
[MPMediaPropertyPredicate predicateWithValue:searchText
                                 forProperty:MPMediaItemPropertyTitle
                              comparisonType:MPMediaPredicateComparisonContains];

MPMediaPropertyPredicate *albumPredicate =
[MPMediaPropertyPredicate predicateWithValue:searchText
                                 forProperty:MPMediaItemPropertyAlbumTitle
                              comparisonType:MPMediaPredicateComparisonContains];

MPMediaPropertyPredicate *artistPredicate =
[MPMediaPropertyPredicate predicateWithValue:searchText
                                 forProperty:MPMediaItemPropertyArtist
                              comparisonType:MPMediaPredicateComparisonContains];

MPMediaQuery *songsQuery = [MPMediaQuery songsQuery];
[songsQuery addFilterPredicate:songNamePredicate];
[songsQuery addFilterPredicate:artistNamePredicate];
[songsQuery addFilterPredicate:albumNamePredicate];

NSLog(@"%@", [songsQuery items]);

I have this working by running the query with each predicate separately but this seems very inefficient!

4条回答
手持菜刀,她持情操
2楼-- · 2020-02-09 04:34

Combining your predicates this way makes it like "AND" relationship. It means that you are querying for a song that has title, album and name all are matching the search text.

To achive what you are trying to do, you should run 3 queries and combining the results in a proper way.

If you run:

MPMediaPropertyPredicate *artistPredicate =
[MPMediaPropertyPredicate predicateWithValue:searchText
                                 forProperty:MPMediaItemPropertyArtist
                              comparisonType:MPMediaPredicateComparisonContains];

NSSet *predicates = [NSSet setWithObjects: artistPredicate, nil];

MPMediaQuery *songsQuery =  [[MPMediaQuery alloc] initWithFilterPredicates: predicates];

NSLog(@"%@", [songsQuery items]);

This will return you with artists matching your search. The same you should do for songs and albums.

If you this this is slow, you may retrieve all the media at once and filter it manually:

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];
    /* Filter found songs here manually */
}
查看更多
相关推荐>>
3楼-- · 2020-02-09 04:35

There are two approaches here:

  1. Use multiple MPMediaPropertyPredicates to get everything using queries. Using this approach, you will need to do multiple queries and aggregate the results yourself. NSSet is your friend!

  2. Get everything (or close to it) from the library and then do your search or filter after that.

Access to the music library can be VERY slow on older devices or versions of the OS. In my own experience implementing something very similar to what you are describing I got the best results with approach 2. Once I had, well, just about everything I then iterated over those items using a scatter/gather approach (my needs did not allow the use of NSPredicate for this). This was actually much more performant than approach 1 for me, though I know that with iOS 6 that gap has closed somewhat.

查看更多
Melony?
4楼-- · 2020-02-09 04:35

Swift 3+ solution for filtering items after running the query, similar to Mohammed Habib's answer:

let searchString = “string to search”
let query = MPMediaQuery()

let filteredItems = query.items?.filter { item -> Bool in
    let properties = [item.songTitle,
                      item.artist,
                      item.albumTitle]

    let lowercasedProperties = properties.compactMap { $0.lowercased() }

    return lowercasedProperties.contains(searchString.lowercased())
}

print(filteredItems ?? [])
查看更多
甜甜的少女心
5楼-- · 2020-02-09 04:41

this works for me:

MPMediaQuery *searchQuery = [[MPMediaQuery alloc] init];
NSPredicate *test = [NSPredicate predicateWithFormat:@"title contains[cd] %@ OR albumTitle contains[cd] %@ OR artist contains[cd] %@", searchString, searchString, searchString];
NSArray *filteredArray = [[searchQuery items] filteredArrayUsingPredicate:test];

//NSLog(@"%@", [filteredArray valueForKeyPath:@"title"]);

for (MPMediaItem *song in filteredArray) 
{
    [arrayOfSongItems addObject:song];
}

I basically filter after getting all items from media query. I don't think my solution is better than filtering the query at the first place, but definitely better than searching three times separately. Or iterating through the array multiple times.

查看更多
登录 后发表回答