Your app design is a simple, one screen, affair that I’ve named DrumDub. At the bottom are controls for choosing a track from your music library and for pausing and resuming playback. At the top you’ll find information about thetrack that’s playing. In the middle are buttons to add percussive sounds, all shown in Figure 9-1.
Figure 9-1. DrumDub rough sketch
You’ll start by building the iPod music playback. Later you’ll add the album artwork, and finally mix in the percussion sounds. As always, start by creating a new Xcode project:
1. Use the Single View Application Xcode template
2. Name the project DrumDub
3. Set the class prefix to DD
4. Set the device to iPhone (see note)
5. Save the project
6. In the project’s supported interface orientations,turn off landscape left and right
Adding a Music Picker
The first step is to create an interface so the user can choose a song, or songs, from their iPod music library. After Chapter 7 (where you used the photo library picker), you shouldn’t be surprised
to learn that iOS provides a ready-made music picker interface. All you have to do is configure it and present it to the user.
You’ll present the music picker interface when the user taps the “Song” button in the interface. For that you’ll need an action. Start by declaring this action in the @interface section of your DDViewController.h file:
- (IBAction)selectTrack:(id)sender;
The MPMediaPickerController class provides the music picker interface. Your -selectTrack: method will create a new MPMediaPickerController, configure it, and present it to the user. Just like the
photo library picker, your app finds out what the user picked via delegate methods. While you’re still editing DDViewController.h, make your DDViewController an MPMediaPickerControllerDelegate:
@interface DDViewController : UIViewController <MPMediaPickerControllerDelegate>
You’ll notice that Xcode is now flagging this line with a compiler error. That’s because the media picker and player header files are not part of the standard UIKit frameworks. Import the definition ofMPMediaPickerControllerDelegate (along with all of the other music player and picker symbols) by adding the following #import statement immediately after your other #import statements:
#import <MediaPlayer/MediaPlayer.h>
Switch to the Main.storyboard Interface Builder file. In the object library, find the Toolbar object. Drag a toolbar into your interface, positioning it at the bottom of the view. The toolbar already includes a bar button item. Select it and change its title property to Song. Connect its sent action (control/right-drag) to the View Controller’s -selectTrack: action, as shown in Figure 9-2.
Figure 9-2. Adding the toolbar and “Song” button
Now you’re ready to write your -selectTrack: action. Switch to the DDViewController.m file and add this code to your @implementation section:
- (IBAction)selectTrack:(id)sender
{
MPMediaPickerController *picker = [[MPMediaPickerController alloc]
initWithMediaTypes:MPMediaTypeAnyAudio];
picker.delegate = self; picker.allowsPickingMultipleItems = NO; picker.prompt = @"Choose a song";
[self presentViewController:picker animated:YES completion:nil];
}
This code creates a new MPMediaPickerController object that will let the user choose any audio type. The media picker is rather flexible, and can be set up to present various types of audio and/or video content on the device.The categories for audio content are:
n Music (MPMediaTypeMusic)
n Podcasts (MPMusicTypePodcast)
n Audiobooks (MPMediaTypeAudioBook)
n iTunes U (MPMediaTypeITunesU)
By combining these binary values together, you can configure your media picker to present any combination of those categories you desire. The constant MPMediaTypeAnyAudio includes all categories, allowing the user tochoose any audio item in their library. A similar set of flags allows video content.
You then make your DDViewController object the picker’s delegate. Next, the option to allow picking multiple tracks at once is disabled. The user will only be able to choose one song at a time. Set a prompt, or title, so the userknows what you’re asking them to do.
Finally, the controller is presented, allowing it to take over the interface and choose a song. This is enough code to see it working, so give it a try. Set the project’s scheme to your iOS device and click the Run button, as shownin Figure 9-3. The toolbar appears, you can tap the “Song” button to bring up the music picker, browse your audio library, and choose a song.
Figure 9-3. Testing the audio picker
You don’t have to use the media picker to choose items from the user’s iPod library. It’s just the most convenient method.
It’s possible to create your own interface, or not have an interface at all. The iPod framework provides classes that allow your app to explore and search the user’s media collection as if it was a database. (Come to think of it, itis a database, so that description is literally true.)
You do this by creating a query object that defines what you’re searching for. This can be as simple as “all R&B songs” or more nuanced, such as “all tracks longer than 2 minutes, belonging to the ‘dance’ genre, with a BPM tagbetween 110 and 120.”
The result is a list of media items matching that description, which you can present any way you like (cough—table—cough).
You can read more about this in the iPod Library Access Programming Guide that you will find in Xcode’s Documentation and API Reference. Read the section “Getting Media Items Programmatically” to get started.
Using a Music Player
What happens next is, well, nothing happens next. When the user picks a track, or taps the Cancel button, your delegate receives one of these messages:
-mediaPicker:didPickMediaItems:
-mediaPickerDidCancel:
Nothing happened, because you haven’t written either. Start by writing -mediaPicker:didPickMe diaItems:. This method will retrieve the audio track the user picked and start it playing using an MPMusicPlayerController object.
First, define a private instance variable and a readonly property so you can keep a reference to,
and easily request, your music player object. Add the following bold code to the private @interface
section at the beginning of the DDViewController.m file:
@interface DDViewController ()
{
MPMusicPlayerController *music; // (store for @property musicPlayer)
}
@property (readonly,nonatomic) MPMusicPlayerController *musicPlayer;
@end
Now you’re ready to implement the first delegate method:
- (void)mediaPicker:(MPMediaPickerController*)mediaPicker didPickMediaItems:(MPMediaItemCollection*)mediaItemCollection
{
if (mediaItemCollection.count!=0)
{
[self.musicPlayer setQueueWithItemCollection:mediaItemCollection]; [self.musicPlayer play];
}
[self dismissViewControllerAnimated:YES completion:nil];
}
The mediaItemCollection parameter contains the list of tracks the user picked. Remember that the picker can be used to choose multiple items at once. Since you set the allowsPickingMultipleItems property to NO, your picker willalways return a single item.
We double check to see that at least one track was chosen (just to be sure) and then use the collection to set the music player’s playback queue. The playback queue is a list of tracks to play and works just like a playlist. Inthis case, it’s a playlist of one. The next statement starts the music playing. It’s that simple.
So what’s the problem with this code? The problem is there is no musicPlayer object yet! Write a property getter method for musicPlayer that lazily creates the object:
- (MPMusicPlayerController*)musicPlayer
{
if (music==nil)
{
music = [MPMusicPlayerController applicationMusicPlayer]; music.shuffleMode = NO;
music.repeatMode = NO;
}
return music;}
You obtain the MPMusicPlayerController object using the +applicationMusicPlayer class method.
This creates an application music player (see the “Application and iPod Music Players” sidebar).
The music player inherits the current iPod playback settings for things like shuffle and repeat modes.
You don’t want any of that, so you turn them off.
Your app has access to two different music player objects. The application music player belongs to your app. Its current playlist and settings exist only in your app, and it stops playing when your app stops.
You can also request the iPod music player object, using [MPMusicPlayerController iPodMusicPlayer].
The iPod music player object is a direct connection to the iPod player in the device. It reflects the current state of music playing in the iPod app. Any changes you make (like pausing playback or altering shuffle mode) changethe iPod app. Music playback continues after your app stops.
There’s only one quirk. The iPod music player object won’t report information about media that’s being
streamed, say via home sharing. But other than that, the iPod music player object is a transparent extension of the built-in iPod app, and allows your app to participate in, and integrate with, the user’s current music activity.
Only one music player can be playing at a time. If your app starts an application music player, it takes over the music playback service, causing the built-in iPod player to stop. Likewise, if your application music player isplaying and the user starts the iPod player, your music player is stopped.
Now toss in a delegate method to handle the case where the user declines to choose a track:
- (void)mediaPickerDidCancel:(MPMediaPickerController*)mediaPicker
{
[self dismissViewControllerAnimated:YES completion:nil];
}
Your basic playback code is now complete. Run your app, choose a track, and enjoy the music.
The MPMusicPlayerController object is self-contained. It takes care of all of the standard iPod behavior for you. It will, for example, automatically fade out if interrupted by an alarm or incoming call, or stop playback when the userunplugs their headphones. I’ll talk a lot more about these events later in this chapter.
That’s not to say you can’t influence the music player. In fact, you have a remarkable amount of control over it. You can start and stop the player, adjust the volume, skip forwards or backwards in the playlist, set shuffle andrepeat modes, change the playback rate, and more. The player will
also tell you a lot about what it’s doing and playing. Using these properties and methods, you could create your own, full-featured, music player.
For this app, you don’t need a full-featured music player. But it would be nice to at least know what’s playing and be able to pause it. Get ready to add that next.
Adding Playback Control
Start by adding some buttons to pause and play the current song. These buttons will need actions, so add these two method declarations to your DDViewController.h file:
- (IBAction)play:(id)sender;
- (IBAction)pause:(id)sender;
You’ll also need to update the state of the play and pause buttons, so add some connections for that:
@property (weak,nonatomic) IBOutlet UIBarButtonItem *playButton;
@property (weak,nonatomic) IBOutlet UIBarButtonItem *pauseButton;
Switch to your Main.storyboard file and add the following objects to the toolbar, inserting them to the left of the “Song” button, in order, as shown in Figure 9-4:
1. A Flexible Space Bar Button Item
2. A Bar Button Item, changing its style to Plain, its identifier to Play, and unchecking enabled
3. A Bar Button Item, changing its style to Plain, its identifier to Pause, and unchecking enabled
4. A Flexible Space Bar Button Item
Figure 9-4. Adding controls to the toolbar
Finally, set all of the connections. Right/control+click on the play button and connect its action to the -play: action (in the View Controller), and the pause button to the -pause: action. Select the View Controller object and usethe connections inspector to connect the playButton outlet to
the play button, and the pauseButton outlet to the pause button.
With the interface objects created and connected, consider for a moment how these buttons should work.
You want:
n the play button to be active (tappable) when the music player is not currently playing
n the play button’s action to start the music playing
n the pause button to be active when the music player is playing
n the pause button’s action to pause the music player
The button’s actions will start and stop the music player. You’ll need to update the enabled state of the buttons whenever the player starts or stops playing. The first part is pretty simple. In DDViewController.m, add theimplementation for the -play: and -pause: actions:
- (IBAction)play:(id)sender
{
[self.musicPlayer play];
}
- (IBAction)pause:(id)sender
{
[self.musicPlayer pause];
}
The second half is updating the button states (enabling or disabling them) at the appropriate times.
Receiving Music Player Notifications
The music player runs in a background thread. Normally, it plays tracks in its playlist until it runs out and stops. It can also pause in response to external events: the user presses the pause button on their headphone cable orthey unplug the iPod from a dock. How do you think your app will learn about these events?
If you said, “by receiving a delegate message or notification,” give yourself a big round of applause! Reading the documentation for the MPMusicPlayerController class, you discover that the music player will optionally send notifications whenever important changes occur, which happen to include when it starts or stops playing. To be notified of those events, you’ll need to register your controller object to receive them. As you remember fromChapter 5, to receive notifications you must:
n Create a notification method
n Register with the notification center to become an observer for the notification
Start by adding this notification method to your DDViewController.m implementation:
- (void)playbackStateDidChangeNotification:(NSNotification*)notification
{
BOOL playing = (music.playbackState==MPMoviePlaybackStatePlaying);
_playButton.enabled = !playing;
_pauseButton.enabled = playing;
}
Also add a method prototype to the private @interface section at the beginning of the source file:
- (void)playbackStateDidChangeNotification:(NSNotification*)notification;
Your notification handler examines the current playbackState of your music player. The player’s playback state will be one of stopped, playing, paused, interrupted, seeking forward, or seeking backwards. In thisimplementation, the only likely states are playing, stopped, interrupted, and paused.
If the player is playing, the pause button is enabled and the play button is disabled. If it’s not playing, the opposite occurs. This presents the play button as an option whenever the player is not playing, and the pause buttonwhen it is.
Your controller won’t receive these notifications until two additional steps are taken. First, you must register to receive these notifications. In the -musicPlayer getter method, add this new code immediately after the playerobject is created and configured (at the end of the if { ... } block):
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self
selector:@selector(playbackStateDidChangeNotification:) name:MPMusicPlayerControllerPlaybackStateDidChangeNotification
object:music];
The second step is to enable the music player’s notifications.MPMusicPlayerController does not,by defaults,end these notifications. You must explicitly request that it does. Immediately after the above code, add one moreline:
[music beginGeneratingPlaybackNotifications];
Your playback controls are now finished. Run your app and see that they work, as shown in Figure 9-5.
Figure 9-5. Working playback controls
Both buttons start out disabled. When you choose a track to play, the pause button becomes active (the middle of Figure 9-5). If you pause the song, or let it finish playing, the play button becomes active (on the right in Figure9-5).
MVC AT WORK
You’re watching the model-view-controller design pattern at work—again. In this scenario, your music player (despite the fact the it’s called a “music controller”) is your data model. It contains the state of the music playback.Whenever that state changes, your controller receives a notification and updates the relevant views—in this case, the play and pause buttons.
You didn’t write any code to update the play or pause button when you start or stop the player.
Those requests are
just sent to the music player. If one of those requests results in a state change, the music player posts the appropriate notifications, and the affected views are updated.
While functional, your app lacks a certain je ne sais quoi. Oh, who are we kidding? This interface is as dull as dishwater! Let’s spruce it up a bit.
Adding Media Metadata
A colorful aspect of the music player object is its nowPlayingItem property. This property returns an object containing metadata about the song that’s playing. The object works like a dictionary, revealing all kinds of interestingtidbits about the current song. This includes information like its title, the artist, the track number, the musical genre, any album artwork, and much more.
For your app, you’ll add an image view
to display the album’s cover and text fields to show the song’s title, the album it came from, and the artist. Start by adding new interface objects to Main.storyboard.
Creating a Metadata View
Select the Main.storyboard file. Using the object library, find the Image View object and add one to the interface. Using the size inspector, set both its width and height to 140 pixels, and position it (using the guides) in theupper-left corner of the view, as shown in Figure 9-6.
Figure 9-7. Adding a metadata label
Make two more labels, just like it, and position them below the first. You can either copy and paste the first label object, or hold down the Option key and drag a copy of the first label to make another. Choose Editor ➤Resolve Auto Layout Issues ➤ Add Missing Constraints in View Controller.
As a final touch, select the root view object and change its background to Black Color. When finished, your interface should look something like the one in Figure 9-8.
Figure 9-8. Finished metadata interface
You know what’s coming next. I’m going to ask you to switch to DDViewController.h, add some outlet properties, and then switch back to Main.storyboard to connect them.
Well, I’m not. Sure, you’re going to create and connect some outlets, but I’m going to show you a nifty Xcode trick so you don’t have to switch back and forth between the files.
While still looking at the Main.storyboard file, switch to the assistant editor view ( View ➤ Assistant Editor ➤ Show Assistant Editor), as shown in Figure 9-9. If your workspace window is a little cramped, hide the utilities area (View ➤ Utilities ➤ Hide Utilities) or collapse the storyboard’s object outline, both shown in Figure 9-9.
Figure 9-9. Viewing Main.storyboard in the assistant editor
When viewing an Interface Builder file, Xcode’s assistant editor conveniently places the interface file of the scene in the right-hand pane. (If it doesn’t, choose the DDViewController.h file from the navigation menu, immediatelyabove the right-hand pane, as shown in
figure 9-9.) Those little circles next to the property and action declarations work just like the ones in the connections inspector.
If you’re not excited already, you should be. It means you can declare an outlet or action, and then connect it to an interface object, without switching between files. How cool is that?
Add these four new outlets to DDViewController.h (now on the right-hand side of the editing area):
@property (weak,nonatomic) IBOutlet UIImageView *albumView;
@property (weak,nonatomic) IBOutlet UILabel *songLabel;
@property (weak,nonatomic) IBOutlet UILabel *albumLabel;
@property (weak,nonatomic) IBOutlet UILabel *artistLabel;
Now drag the connections that appear to the left of those declarations and connect them directly to the interface objects, as shown in Figure 9-10.
Figure 9-10. Connecting outlets directly from the interface file
You created the outlet properties, and connected them to the interface object, using a single window. Switch back to the standard editor ( View ➤ Standard Editor ➤ Show Standard Editor) and select the DDViewController.mfile. It’s time to write the code to update these new interface objects.









No comments:
Post a Comment