Ben Dodson

Freelance iOS, Apple Watch, and Apple TV Developer

MPMediaItem+CanAddToLibrary.swift

Since iOS 9.3 it has been possible to add Apple Music tracks to the media library as such:

let library = MPMediaLibrary()
library.addItem(withProductID: id) { (entity, error) in
	if let error = error {
		NSLog("Error: \(error.localizedDescription)")
	}
}

This is powerful as you can use a simple identifier to both play a song and add it to the library but it is likely that your UI will want to show an add to library button similar to the Music app on iOS. To remedy this, I’ve created a simple Swift extension1 for MPMediaItem that tells you if a currently playing track is available in your library:

import UIKit
import MediaPlayer

extension MPMediaItem {

    var canAddToLibrary: Bool {
        let id = MPMediaPropertyPredicate(value: persistentID, forProperty: MPMediaItemPropertyPersistentID)
        let query = MPMediaQuery(filterPredicates: [id])
        let count = query.items?.count ?? 0
        return count == 0
    }
    
}

Why an extension on MPMediaItem? The only way to know if a track is in your library is to search the user library with an MPMediaQuery. Unfortunately you can’t search on the MPMediaItemPropertyPlaybackStoreID (as some tracks may not be on Apple Music) so instead you need to use the persistent ID property. If you try and play an Apple Music track using an identifier, then you can retrieve an MPMediaItem and use that to get the persistent ID for searching the media library. I use this in my own apps2 by listening to the MPMusicPlayerControllerNowPlayingItemDidChange notification and then checking if there is a nowPlayingItem on my MPMusicPlayerController instance; if there is then check it to find the current status:

var player = MPMusicPlayerController()

override func viewDidLoad() {
    super.viewDidLoad()
    
    player.beginGeneratingPlaybackNotifications()
    NotificationCenter.default.addObserver(self, selector: #selector(playbackStateDidChange), name: .MPMusicPlayerControllerNowPlayingItemDidChange, object: nil)
}


@objc func playbackStateDidChange() {
    
    guard let item = player.nowPlayingItem else {
        return
    }

    // at this point we do not know if the track can be added - any UI for adding should be hidden

    if item.canAddToLibrary {
    	// show your "Add to library" button
    } else {
    	// show some UI to explain "Already in library"
    }
}

I am fairly sure this is how the Music app works on iOS as you’ll notice when skipping tracks that the UI for the track status is completely hidden until the track is ready to play at which point either an add button or a tick will appear. One thing that caught me out was listening for MPMusicPlayerControllerPlaybackStateDidChange but this seems to fire inconsistently both on iOS 10 and iOS 11 (unless you are on an iPad and run the Music app in split-screen mode in which case it always works) - checking for MPMusicPlayerControllerNowPlayingItemDidChange works consistently and will still yield an MPMediaItem with which to work with.

IMPORTANT: The user will need to have granted permission to access their media library in order for this extension to work. It will crash your app if you do not have NSAppleMusicUsageDescription in your Info.plist (although that is the bare minimum - you should actively check for capabilities before using this as no point showing an “Add to library” button if the user doesn’t have that capability!)

Checkout the MPMediaItem+CanAddToLibrary Swift extension on GitHub

  1. I wrote the extension using Swift 4 with Xcode 9 / iOS 11 SDK but it should work just fine in Swift 3 as it isn’t using any new language stuff. ↩︎

  2. Including an exciting iOS 11 only Apple Music app I’m working on. If you’re an Apple Music subscriber with the iOS 11 beta installed (developer or public), contact me for a test version before it launches in September… ↩︎

Forcing left-to-right text in iOS localizations » « Flawless