Tuesday, 6 May 2014

Design [Sweet, Sweet Music]

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 thats 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 projects 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 shouldnt 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. Thats 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 Controllers -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  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 (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 pickers 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 projects 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 users iPod library. Its just the most convenient method.

Its 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 users 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 youre searching for. This can be as simple as all R&B songs or mornuanced, 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 Xcodes 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 havent 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 players playback queue. The playback queue is a list of tracks to play and works just like a playlist. Inthis case, its a playlist of one. The next statement starts the music playing. Its that simple.

So whats 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 dont 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.

Theres only one quirk. The iPod music player object won’t report information about media thats 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 users 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.

Thats not to say you cant 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 its doing and playing. Using these properties and methods, you could create your own, full-featured, music player.

For this app, you dont need a full-featured music player. But it would be nice to at least know whats 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, sadd 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 buttons action to start the music playing

n     the pause button to be active when the music player is playing

n     the pause buttons action to pause the music player

The buttons 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 players 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 its 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 wont 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 players 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
Youre watching the model-view-controller design pattern at work—again. In this scenario, youmusic player (despite thfact the its called a “musi controller” is your data model. It contains the state of the music playback.Whenever that state changesyour controller receives a notification and updates the relevant views—i this case, the play and pausbuttons.

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! Lets 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 thats 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 albums cover and text fields to show the songs 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 whats 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 dont 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 storyboards object outline, both shown in Figure 9-9.


Figure 9-9. Viewing Main.storyboard in the assistant editor

When viewing an Interface Builder file, Xcodes assistant editor conveniently places the interface file of the scene in the right-hand pane. (If it doesnt, 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, anconnected them to the interface object, using a single windowSwitch back to the standard editor ( View  Standard Editor  Show Standard Editor) and select the DDViewController.mfile. Its time to write the code to update these new interface objects.

No comments:

Post a Comment