Advertisement

Build an iOS Music Player: UI Theming

by

This three-part tutorial series will teach you how to build a custom music player with the iOS SDK. Read on!


Series Format:


Welcome to the third and last part of this tutorial series about building a custom music player with the iOS SDK. In this last installment, we will finish our now playing screen and we will give our app a custom design with the UIAppearance Protocol. Once our music player is finished, it will look like the default iPod from iOS 6.

    


Step 1: Adding the Images

Download the source code attached to this project and drag all the images from the folder titled Images Part 3 into the project. Make sure "Copy items into destination group’s folder (if needed)" is checked and click "Finish".

    


Step 2: Design the Now Playing Screen

Open the Storyboard and select the View Controller (not the view). Open the Attributes Inspector and select the "Hides Bottom Bar on Push" checkbox. Also change the Bottom bar in the Simulated Metrics section from "Inferred" to "None". Now select the view and give it a black background. If you're ready with that, drag an image view from the Object Library on the view and set the Mode from "Scale to Fill" to "Aspect Fit". With these options set, the album artwork won’t be stretched out to fit the image view. Now open the Size Inspector and change its size to 320 by 320 pixels. Also modify the autoresizing as follows:

    

With this autoresizing setup, the image will keep on top of the view and won’t be stretched out on a iPhone with a 3.5" Retina screen. Now drag another image view onto the current view and modify the size options to look as follows:

    

With this size options the image view will stay at the bottom of the screen. We adjust this autoresizing option so our music player will look good on the newest iPhone 5, but also on an older iPhone or iPod touch. Now open the Attributes Inspector and set the Image to "Music-controls-background.png".

Now that we have created the background for the controls, let's next create the controls themselves. First drag a slider from the Object Library on top of the image view we just created. Change the autoresizing settings, so they look the same as the controls image view. After that give the slider a size of 228 by 23 pixels, set the X to 46 and set the Y to 470. Now open the Attributes inspector and set the "Min Track Tint" to a really dark color, so it’s almost black. You can leave the "Max Track Tint" to default.

Drag three buttons from the Object Library on top of the controls image view, change their type to "Custom", delete the text and select the "Show Touch On Highlight" checkbox. Set the image of the first button to Previous-icon.png, the image of the second button to Play-icon.png and the image of the third button to Next-icon.png. Now open the Size Inspector and change the autoresizing settings so they are the same as the slider’s. Now change the size and position of the buttons as follows:

  • Button 1: width: 106 height: 47 X: 0 Y: 408
  • Button 2: width: 108 height: 47 X: 106 Y: 408
  • Button 3: width: 106 height: 47 X: 214 Y: 408

Now the interface of the now playing screen should look like the following:

    

The last thing we need to do for the design of the now playing screen is to add three labels for the song information. First, drag a view from the Object Library on top of the navigation bar. Change the background to "Clear Color", set the width to 196 pixels and the height to 36 pixels. The X an Y should automatically adjust themselves. This view will contain the 3 labels for the song artist, title, and album names. Now drag three labels into the view we just added. Delete the text, set the Alignment to center and change the font to "System 12.0" of all the labels. Now change the size and position of the labels as follows:

  • Label 1: width: 196 height: 12 X: 0 Y: 0
  • Label 2: width: 196 height: 12 X: 106 Y: 12
  • Label 3: width: 196 height: 12 X: 214 Y: 24

At last, select the second label and change the font to "System Bold 12.0". This label will be for the songs title, so with a bold font it will be more prominent.

Now that we have finished the standard design of our now playing screen, I think it’s a good time to test our application. Click Build and Run to test the app. Play a song to go to the now playing screen. You should be able to see the controls, but of course they won’t work.


Step 3: Making the Outlets and Actions

To make our now playing screen work, we first need to create some new files. Go to "File" > "New" > "File..." to create a new file. Select "Objective-C class" and then click "Next". Enter "NowPlayingViewController" for the class and make sure that it’s a subclass of UIViewController and that both the checkboxes are not selected. Click "Next" again and then click "Create".

Open NowPlayingViewController.h and modify the code to read as follows:

#import <UIKit/UIKit.h>
#import <MediaPlayer/MediaPlayer.h>

@interface NowPlayingViewController : UIViewController
{
    
    MPMusicPlayerController *musiPlayer;
    
    IBOutlet UIImageView *artworkImageView;
    
    IBOutlet UIButton *playPauseButton;
    IBOutlet UISlider *volumeSlider;
    
    IBOutlet UILabel *artistLabel;
    IBOutlet UILabel *titleLabel;
    IBOutlet UILabel *albumLabel;
    
    
}

@property (nonatomic, retain) MPMusicPlayerController *musicPlayer;

- (IBAction)playPause:(id)sender;
- (IBAction)nextSong:(id)sender;
- (IBAction)previousSong:(id)sender;
- (IBAction)volumeSliderChanged:(id)sender;

- (void) registerMediaPlayerNotifications;

@end

Here we first import the MediaPlayer framework, then we created an MPMusicPlayerController object which we will use to control the music. After that, we create some outlets for the interface elements and at last we create the actions.

Now open the Storyboard and select the View Controler. Open the Identity Inspector and change the Class to the NowPlayingViewController we just created. Then open the Connections Inspector and connect the outlets as follows:

  • Connect the albumLabel outlet to the first label in the navigation bar.
  • Connect the artistLabel outlet to the third label in the navigation bar.
  • Connect the artworkImageView outlet to large the image view.
  • Connect the playPauseButton outlet to the button with the play icon.
  • Connect the titleLabel outlet to the second label in the navigation bar.
  • Connect the volumeSlider outlet to the slider.

Now connect the actions as follows:

  • Drag from the nextSong: action to the button with the next icon and select "Touch Up Inside" from the pop-up menu.
  • Drag from the playPause: action to the button with the play icon and select "Touch Up Inside" from the pop-up menu.
  • Drag from the previousSong: action to the button with the previous icon and select "Touch Up Inside" from the pop-up menu.
  • Drag from the volumeSliderChanged: action to the slider and select "Value Changed" from the pop-up menu.

Step 4: Updating the UI Upon Load

Now that we have setup our outlets and actions open the NowPlayingViewController.m and add the following line under @implementation NowPlayingViewController:

@synthesize musicPlayer;

Next, go the the viewDidLoad method and modify the code to read as follows:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    
    musicPlayer = [MPMusicPlayerController iPodMusicPlayer];
    [self registerMediaPlayerNotifications];
    
    
    UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@"Back" style:UIBarButtonItemStyleBordered target:self action:@selector(back)];
    self.navigationItem.leftBarButtonItem = backButton;
    
    
    artistLabel.textColor = [UIColor colorWithRed:0.278 green:0.278 blue:0.278 alpha:1.0];
    artistLabel.shadowColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
    artistLabel.shadowOffset = CGSizeMake(0, 1);
    
    titleLabel.textColor = [UIColor colorWithRed:0.278 green:0.278 blue:0.278 alpha:1.0];
    titleLabel.shadowColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
    titleLabel.shadowOffset = CGSizeMake(0, 1);
    
    albumLabel.textColor = [UIColor colorWithRed:0.278 green:0.278 blue:0.278 alpha:1.0];
    albumLabel.shadowColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
    albumLabel.shadowOffset = CGSizeMake(0, 1);
}

Here we first set our music player controller to an iPodMusicPlayer. This basically means that our app shares the iPod state and if we quit our app the music will continue playing. Our app will also have the shuffle and repeat mode.

The applicationMusicPlayer is the other type. This music player plays the music locally within your app. Your music player can have a different now playing item than the built-in iPod app. When you quit the app, the music stops playing. After that, we call the registerForMediaPlayerNotifications method where we will register three observers for the media player notifications. We will create that method later in this tutorial. Next we create a bar button item with the title "Back" and make it the left bar button item. We do this because a default back button will have the title of the previous view controller and that can be a long title, which we don’t want for the now playing screen. At last, we change the color of the labels and give it a shadow.

Next, add the following methods under the viewDidLoad section:

- (void) back
{
    [self.navigationController popViewControllerAnimated:YES];
}

- (void) viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:YES];
    
    // update control button
	
	if ([musicPlayer playbackState] == MPMusicPlaybackStatePlaying) {
        
        [playPauseButton setImage:[UIImage imageNamed:@"Pause-icon.png"] forState:UIControlStateNormal];
	} else {
        [playPauseButton setImage:[UIImage imageNamed:@"Play-icon.png"] forState:UIControlStateNormal];
    }
    
    
    // Update volume slider
    
    [volumeSlider setValue:[musicPlayer volume]];
    
    
    // Update now playing info
    
    MPMediaItem *currentItem = [musicPlayer nowPlayingItem];
    
    MPMediaItemArtwork *artwork = [currentItem valueForProperty: MPMediaItemPropertyArtwork];
    UIImage *artworkImage = [artwork imageWithSize: CGSizeMake (320, 320)];
    
    if (!artworkImage) {
        artworkImage = [UIImage imageNamed:@"No-artwork.png"];
    }
    
    [artworkImageView setImage:artworkImage];
    
    
    NSString *titleString = [currentItem valueForProperty:MPMediaItemPropertyTitle];
    if (titleString) {
        titleLabel.text = titleString;
    } else {
        titleLabel.text = @"Unknown title";
    }
    
    NSString *artistString = [currentItem valueForProperty:MPMediaItemPropertyArtist];
    if (artistString) {
        artistLabel.text = artistString;
    } else {
        artistLabel.text = @"Unknown artist";
    }
    
    NSString *albumString = [currentItem valueForProperty:MPMediaItemPropertyAlbumTitle];
    if (albumString) {
        albumLabel.text = albumString;
    } else {
        albumLabel.text = @"Unknown album";
    }
    
}

The first method gets called when you press the back button and will pop the view controller with an animation. The second method gets called every time the view appears. We use these methods to update our interface and the information about the current playing track. First, we update the play/pause button by checking if the music is playing. Next, we set the value of the slider to the current volume of the music player. After that we update the artwork image view. If the current song doesn’t have artwork, we show a default artwork image. At last, we create an NSString to store the title of the currentItem, which is the now playing item. If the currentItem has a title we set the titleLabel to that title, but if not, we set the title to "Unknown title". We do the same for the artist and album.


Step 5: MediaPlayer Notification

Add the following method under the methods we just created:

- (void) registerMediaPlayerNotifications
{
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    
	[notificationCenter addObserver: self
						   selector: @selector (handle_NowPlayingItemChanged:)
							   name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification
							 object: musicPlayer];
	
	[notificationCenter addObserver: self
						   selector: @selector (handle_PlaybackStateChanged:)
							   name: MPMusicPlayerControllerPlaybackStateDidChangeNotification
							 object: musicPlayer];
    
    [notificationCenter addObserver: self
						   selector: @selector (handle_VolumeChanged:)
							   name: MPMusicPlayerControllerVolumeDidChangeNotification
							 object: musicPlayer];
    
	[musicPlayer beginGeneratingPlaybackNotifications];
}

This is the method we called in the viewDidLoad method we changed before. Here, we register three observers for the media player notifications. The first one is for the MPMusicPlayerControllerNowPlayingItemDidChangeNotification. We will use this method to update the current media item information. The second one is for the MPMusicPlayerControllerPlaybackStateDidChangeNotification. We also use this one to update the UI and to pop the view controller if the music player stopped. The third and last one is for the MPMusicPlayerControllerVolumeDidChangeNotification. We will use this one to update the current value of the slider.

Whenever you register an observer, you should also be sure to remove it in the didReceiveMemoryWarning, so go to that method and modify it to read as follows:

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    
    [[NSNotificationCenter defaultCenter] removeObserver: self
													name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification
												  object: musicPlayer];
	
	[[NSNotificationCenter defaultCenter] removeObserver: self
													name: MPMusicPlayerControllerPlaybackStateDidChangeNotification
												  object: musicPlayer];
    
    [[NSNotificationCenter defaultCenter] removeObserver: self
													name: MPMusicPlayerControllerVolumeDidChangeNotification
												  object: musicPlayer];
    
	[musicPlayer endGeneratingPlaybackNotifications];
}

Next, add the following code under the registerMediaPlayerNotifications method:

- (void) handle_NowPlayingItemChanged: (id) notification
{
    
    if ([musicPlayer playbackState] != MPMusicPlaybackStateStopped) {
        MPMediaItem *currentItem = [musicPlayer nowPlayingItem];
        
        MPMediaItemArtwork *artwork = [currentItem valueForProperty: MPMediaItemPropertyArtwork];
        UIImage *artworkImage = [artwork imageWithSize: CGSizeMake (320, 320)];
        
        if (!artworkImage) {
            artworkImage = [UIImage imageNamed:@"No-artwork.png"];
        }
        
        [artworkImageView setImage:artworkImage];
        
         NSString *titleString = [currentItem valueForProperty:MPMediaItemPropertyTitle];
         if (titleString) {
             titleLabel.text = titleString;
         } else {
             titleLabel.text = @"Unknown title";
         }
         
         NSString *artistString = [currentItem valueForProperty:MPMediaItemPropertyArtist];
         if (artistString) {
             artistLabel.text = artistString;
         } else {
             artistLabel.text = @"Unknown artist";
         }
         
         NSString *albumString = [currentItem valueForProperty:MPMediaItemPropertyAlbumTitle];
         if (albumString) {
             albumLabel.text = albumString;
         } else {
             albumLabel.text = @"Unknown album";
         }

    }
}

This method will respond to the MPMusicPlayerControllerNowPlayingItemDidChangeNotification notification. The code we use for this method should be familiar by now, because we almost used the same code for the viewWillAppear method. The only thing we added here is the first if-statement. In that if-statement we check if the music player is not stopped, so the code will only be executed when the music player is playing or paused. We do this because when the music player stops, the view controller will pop with an animation and we still want to see the information about the last played track when that animation is busy.

Add the following code under the handle_NowPlayingItemChanged: method:

- (void) handle_PlaybackStateChanged: (id) notification
{
    MPMusicPlaybackState playbackState = [musicPlayer playbackState];
	
	if (playbackState == MPMusicPlaybackStatePaused) {
        [playPauseButton setImage:[UIImage imageNamed:@"Play-icon.png"] forState:UIControlStateNormal];
        
        
	} else if (playbackState == MPMusicPlaybackStatePlaying) {
        [playPauseButton setImage:[UIImage imageNamed:@"Pause-icon.png"] forState:UIControlStateNormal];
        
	} else if (playbackState == MPMusicPlaybackStateStopped) {
        
        [playPauseButton setImage:[UIImage imageNamed:@"Play-icon.png"] forState:UIControlStateNormal];
		[musicPlayer stop];
    
        [self.navigationController popViewControllerAnimated:YES];
        
	}
    
}

This method will respond to the MPMusicPlayerControllerPlaybackStateDidChangeNotification notification. Here we check the state of the music player and update the image of the playPauseButton. As you can see, we stop the music player if the state is MPMusicPlaybackStateStopped. We do this to ensure that the music player will play its queue from the start. We also pop the view controller when the music player stops, so the user can select a new song to play.

At last, add the following code under the handle_PlaybackStateChanged: method:

 - (void) handle_VolumeChanged: (id) notification
{
    [volumeSlider setValue:[musicPlayer volume]];
}

This method will respond to the MPMusicPlayerControllerVolumeDidChangeNotification notification. Here we update the volume slider to the music player’s volume. We do this because we can also adjust the volume with the hardware buttons.


Step 6: Making the Controls Work

Add the following code under the didReceiveMemoryWarning section:

- (IBAction)playPause:(id)sender
{    
    if ([musicPlayer playbackState] == MPMusicPlaybackStatePlaying) {
        [musicPlayer pause];
        
    } else {
        [musicPlayer play];
    }
}

- (IBAction)nextSong:(id)sender
{
    [musicPlayer skipToNextItem];
}

- (IBAction)previousSong:(id)sender
{
    [musicPlayer skipToPreviousItem];
}

- (IBAction)volumeSliderChanged:(id)sender
{
    [musicPlayer setVolume:volumeSlider.value];
}

In the first action, we check if the music player is playing. If it is playing, we will pause the music player. If it isn’t playing, we start by playing the music.

In the second action, we let the music player skip to the next item and in the third action we let the music player skip to the previous item.

In the fourth and last action, we set the volume of the music player to the value of the slider.

The now playing screen is complete. Next let's test our app before we start giving our music player a custom design. Click Build and Run to test the application. Now all the controls in the now playing screen should work and you should see the information about the current track. The labels are now pretty ugly, but they will look great with our custom design.


Step 7: Making the Status Bar Black

In iOS 6, the status bar is by default the same color as the navigation bar. Because our music player will have a design that looks like the built-in iPod, we will change the status bar to black.

In the navigator area, select the project name (in out case "Music"). Next, select the current target ("Music" here again), and then select the "Summary" tab. Scroll down to the "iPhone / iPod Deployment Info" and then go to the "Status Bar" options. Change the Style from "Default" to "Black Opaque" and now our status bar is black.

    

Step 8: Customizing the Navigation Bar

Since the release of iOS you can customize certain aspects of standard UIKit components with the UIAppearance Protocol. We will use this to set a custom background image for our navigation bar and to change its text properties.

Open AppDelegate.m and add the following code under the application:didFinishWithLaunchingOptions: method:

- (void) createCustomUI
{    
    UIImage *navBarImage = [UIImage imageNamed:@"Navigation-bar.png"];
    [[UINavigationBar appearance] setBackgroundImage:navBarImage forBarMetrics:UIBarMetricsDefault];
    
    [[UINavigationBar appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
                                                          [UIColor colorWithRed:0.278 green:0.278 blue:0.278 alpha:1.0],
                                                          UITextAttributeTextColor,
                                                          [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0],
                                                          UITextAttributeTextShadowColor,
                                                          [NSValue valueWithUIOffset:UIOffsetMake(0, 1)],
                                                          UITextAttributeTextShadowOffset,
                                                          nil]];


}

Here we are setting the navBarImage as a default background image for all the navigations bars. After that, we update the text color, text shadow color, and the text shadow offset for all the navigation bars.

Now go to the application:didFinishWithLaunchingOptions: method and modify it to read as follows:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [self createCustomUI];
    
    return YES;
}

Here we just added [self createCustomUI]; to call the createCustomUI method.

Build and Run the app. All the navigation bars should now be gray with a dark gray title. If you go to the albums section and select an album, you will see that the back button is still blue. We will fix this in a moment.

Go to the createCustomUI method and add the following code:

    UIImage *backButton = [[UIImage imageNamed:@"Nav-bar-back-button.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 6)];
    [[UIBarButtonItem appearance] setBackButtonBackgroundImage:backButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
    
    UIImage *backButtonHighlited = [[UIImage imageNamed:@"Nav-bar-back-button-highlited.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 6)];
    [[UIBarButtonItem appearance] setBackButtonBackgroundImage:backButtonHighlited forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
    
    UIImage *navButton = [[UIImage imageNamed:@"Nav-button.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 6, 0, 6)];
    [[UIBarButtonItem appearance] setBackgroundImage:navButton forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
    
    UIImage *navButtonHighlited = [[UIImage imageNamed:@"Nav-button-highlited.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 6, 0, 6)];
    [[UIBarButtonItem appearance] setBackgroundImage:navButtonHighlited forState:UIControlStateHighlighted barMetrics:UIBarMetricsDefault];
    
    [[UIBarButtonItem appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
                                                          [UIColor colorWithRed:0.278 green:0.278 blue:0.278 alpha:1.0],
                                                          UITextAttributeTextColor,
                                                          [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0],
                                                          UITextAttributeTextShadowColor,
                                                          [NSValue valueWithUIOffset:UIOffsetMake(0, 1)],
                                                          UITextAttributeTextShadowOffset,
                                                          nil] forState:UIControlStateNormal];
    
    [[UIBarButtonItem appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
                                                          [UIColor colorWithRed:0.278 green:0.278 blue:0.278 alpha:1.0],
                                                          UITextAttributeTextColor,
                                                          [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0],
                                                          UITextAttributeTextShadowColor,
                                                          [NSValue valueWithUIOffset:UIOffsetMake(0, 1)],
                                                          UITextAttributeTextShadowOffset,
                                                          nil] forState:UIControlStateHighlighted];

Here we actually do the same as we did for the navigation bar, but there are a few significant changes. For the navigations bar button and back button we can set an image for each different state. As you can see, we changed the images for the UIControlStateNormal and UIControlStateHighlighted. We also made the image resizable, so the corners will always look fine.


Step 9: Customizing the Tab Bar

Next, add the following code to the createCustomUI method:

    [[UITabBar appearance] setBackgroundImage:[UIImage imageNamed:@"Tab-bar.png"]];
    [[UITabBar appearance] setSelectionIndicatorImage:[UIImage imageNamed:@"Tab-bar-selected.png"]];
    
    [[UITabBarItem appearance] setTitleTextAttributes:[NSDictionary dictionaryWithObjectsAndKeys:
                                                       [UIColor colorWithRed:0.278 green:0.278 blue:0.278 alpha:1.0],
                                                       UITextAttributeTextColor,
                                                       [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0],
                                                       UITextAttributeTextShadowColor,
                                                       [NSValue valueWithUIOffset:UIOffsetMake(0, 1)],
                                                       UITextAttributeTextShadowOffset,
                                                       nil] forState:UIControlStateNormal];

Here we update the tab bar for its background image and the image for the selected tab. We also update the title text attributes, so they will be dark gray and have a white shadow at the bottom.


Step 10: Customizing the Table View

At last, add the following code to to the createCustomUI method:

    [[UITableView appearance] setBackgroundColor:[UIColor colorWithRed:0.914 green:0.918 blue:0.925 alpha:1.0]];
    [[UITableView appearance] setSeparatorStyle:UITableViewCellSeparatorStyleNone];

Here we give all the tableviews a light gray background and remove the gray separator line.

Click Build and Run to test the application. The design of the app will look a lot different, but there are still a few more things we need to do.


Step 11: Adding Images to the Tab Bar

Open SongsViewController.m and modify the code to read as follows:

- (void)viewDidLoad
{
    [super viewDidLoad];

    UITabBarItem *item1 = [[self.tabBarController.tabBar items] objectAtIndex:0];
    [item1 setFinishedSelectedImage:[UIImage imageNamed:@"Songs-tab-bar-icon.png"] withFinishedUnselectedImage:[UIImage imageNamed:@"Songs-tab-bar-icon.png"]];
    
    UITabBarItem *item2 = [[self.tabBarController.tabBar items] objectAtIndex:1];
    [item2 setFinishedSelectedImage:[UIImage imageNamed:@"Albums-tab-bar-icon.png"] withFinishedUnselectedImage:[UIImage imageNamed:@"Albums-tab-bar-icon.png"]];

}

Here we first create a tab bar item from the array of all the items of the tab bar controller. Because we have 2 tabs, we use the indices 0 and 1. After that, we update the selected and unselected images of the tab bar items to the same image.

We customize the tab bar items here, because we want a different image for each tab bar item and we can’t receive the array of tab bar items in the AppDelegate.


Step 12: Customize the Table View Cells

Next go to the tableView:cellForRowAtIndexPath: method and add the following code above the return cell:

    cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Table-view-background.png"]];

    cell.textLabel.textColor = [UIColor colorWithRed:0.278 green:0.278 blue:0.278 alpha:1.0];
    
    cell.selectedBackgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Table-view-selected-background.png"]];
    
    cell.textLabel.backgroundColor = [UIColor clearColor];
    cell.detailTextLabel.backgroundColor = [UIColor clearColor];

Here we first update the background view of the cell to a custom image. Then we change the text color of the text label to a dark gray color. After that we update the background view for when the cell is selected to a custom image and at last we set the background color of the labels to clearColor, because the default background color is white.

Do the same for the tableView: cellForRowAtIndexPath: method in the AlbumsViewController.m file and for the song cells in the AlbumViewController.m file. Now add the following code to the album information cell in the AlbumViewController.m file:

        albumArtistLabel.textColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1.0];
        albumInfoLabel.textColor = [UIColor colorWithRed:0.35 green:0.35 blue:0.35 alpha:1.0];

        cell.backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Table-view-album-background.png"]];

Here we give the labels a gray color and the cell a custom image as background view.

Next open the storyboard and select the cell in the songs table view controller. Open the Attributes Inspector and change the Accessory type from "Disclosure Indicator" to "None". Do the same for the cell in the album view controller.

Now that we have finished the design and our music player looks like the built-in iPod, I think it’s a good time to test our app. Click Build and Run to test the app.

Our music player is almost finished, but there is one last thing to do. We still can’t go to the now playing screen without selecting a new song. We will add a button in the navigation bar to go to the now playing screen. This button will only be visible when the music player isn't stopped.


Step 13: Create the Now Playing Button

Open the Storyboard and select the segue from the cell in the songs table view controller to the now playing view controller. Open the Attributes Inspector and set the Identifier to "NewSong". Next CTRL drag from the Songs View Controller (not the cell, or tableview) to the now playing view controller and selected "push from the pop-up menu". Change the identifier of this segue to "NowPlaying".

Now open SongsViewController.h and modify the code to read as follows:

#import <UIKit/UIKit.h>
#import <MediaPlayer/MediaPlayer.h>

@interface SongsViewController : UITableViewController
{
    UIBarButtonItem *nowPlayingButton;
}

@end

Next open SongsViewController.m and add the following code to the viewDidLoad method:

nowPlayingButton = [[UIBarButtonItem alloc] initWithTitle:@"NP" style:UIBarButtonItemStyleBordered target:self action:@selector(goToNowPlaying)];
    
    if ([[MPMusicPlayerController iPodMusicPlayer] playbackState] == MPMusicPlaybackStateStopped) {
        self.navigationItem.rightBarButtonItem = nil;
    } else {
        self.navigationItem.rightBarButtonItem = nowPlayingButton;
    }
    
    NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
    
	[notificationCenter addObserver: self
						   selector: @selector (handle_PlaybackStateChanged:)
							   name: MPMusicPlayerControllerPlaybackStateDidChangeNotification
							 object: [MPMusicPlayerController iPodMusicPlayer]];
    
    [[MPMusicPlayerController iPodMusicPlayer] beginGeneratingPlaybackNotifications];

This code should all be familiar by now, because we used it all before. First we create a bar button item with the title "NP" (Now Playing), then we update the right bar button item of the navigation bar to the now playing button if the music player isn’t stopped. At last, we register an observer for a media player notification, so we can hide or show the now playing button when the playback state of the music player changes.

Now add the following methods under the viewDidLoad method:

- (void) goToNowPlaying
{
    [self performSegueWithIdentifier:@"NowPlaying" sender:self];
}

- (void) handle_PlaybackStateChanged: (id) notification
{
	if ([[MPMusicPlayerController iPodMusicPlayer] playbackState] == MPMusicPlaybackStateStopped) {
        self.navigationItem.rightBarButtonItem = nil;
    } else {
        self.navigationItem.rightBarButtonItem = nowPlayingButton;
    }
    
}

The first method gets called when you press the now playing button and will perform the segue we just created in our storyboard. The second method will hide or show the now playing button.

We also need to remove the observer we created earlier, so go to the didReceiveMemoryWarning method and change the code to read as follows:

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    
    
    [[NSNotificationCenter defaultCenter] removeObserver: self
													name: MPMusicPlayerControllerPlaybackStateDidChangeNotification
												  object: [MPMusicPlayerController iPodMusicPlayer]];
    
	[[MPMusicPlayerController iPodMusicPlayer] endGeneratingPlaybackNotifications];
}

The last thing we need to do is update the prepareForSegue:sender: method, because we don’t want to play a new song when the user presses the now playing button. Go to that method and modify the code read as follows:

- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if ([[segue identifier] isEqualToString:@"NewSong"])
    {
        MPMediaQuery *songsQuery = [MPMediaQuery songsQuery];
        NSArray *songs = [songsQuery items];
        
        int selectedIndex = [[self.tableView indexPathForSelectedRow] row];
        
        MPMediaItem *selectedItem = [[songs objectAtIndex:selectedIndex] representativeItem];
        
        MPMusicPlayerController *musicPlayer = [MPMusicPlayerController iPodMusicPlayer];
        
        [musicPlayer setQueueWithItemCollection:[MPMediaItemCollection collectionWithItems:[songsQuery items]]];
        [musicPlayer setNowPlayingItem:selectedItem];
        
        [musicPlayer play];
    }
}

Now we first check if the identifier of the segue is "NewSong" for obvious reasons.

Do the same for the AbumsViewController and the AlbumViewConroller, so you can also go from there to the now playing screen.


Wrap Up

Thanks for reading this tutorial about creating a custom music player with the iOS SDK. In this third and last part, we covered how to control the music player and get the information about the current playing song. We also learned how to create a custom design with the UIAppearance Protocol. I hope you liked this tutorial series and if you have questions or comment on this tutorial, leave them in the comments section below!

Advertisement