Music Streaming App DetailViewController using UIStackView in Swift
In the previous post we designed the model and also built out the ListViewController for our music app. UIStackViews and Autolayout .In this tutorial post we will be learning how to build a detailed view controller using UIStackview in Swift for iOS9.
Lets get started
- Create a new file which subclasses UIViewController and call it MusicDetailViewController.swift
- Create a new variable to hold the MusicTrack object which will be set by the ListViewController.
- Lets drag a UIViewController on the main storyboard next to the tableviewcontroller. Change the class of this file to MusicDetailViewController
- CTRL+DRAG from UITableViewCell on to this view controller and select push type. Also select the segue and change the identifier to "detail"
- Drag an imageview on to the UIViewController and set the constraints as below. This will be our background image which we will use for providing the blur effect.
- Now drag a Visual Blur effect view on top the image view and set the same constraints even for the visual blur view. If you see any warning make sure you update the frames.
- Drag a vertical UIStackView on to the UIViewController and then add constraints of 25 on all the side. Select the stackview and update the frames by going to the pin icon at the bottom right of the interface builder.
- Now drag 3 button on top of the StackView. Select all the 3 button and then select the stack button. Change the axis to horizontal and then distribution to fill equally.
- Now drag an imageview in the vertical stack view just above the stackview which embeds all the buttons. Feel free to adjust the size of the image view . This will be our poster imageview.
- Open up the assistant editor and then CTRL+DRAG from the background image and call it bkImageView
- Similarly create IBOutlet by CTRL+DRAG ing the poster Image View and called it posterImageView
- Now create IBOutlets for all the buttons and call prev,play and next button. ( Left to right order of buttons)
- Also create IBAction from all the three button. I have named them prev,play and next ( Left to right order of buttons)
Loading Image in the ImageView
We will use the artWorkUrl and get the imageData as NSData by downloading the data from the internet. Then we will set the imageView.image with UIImage(data: NSData) method.
Playing Audio Using AVPlayer
In the musicTrack model object we have a previewUrl which links to a url for the audio, we will be using that in this app. You could generalize it to use any api which provides a link to the Audio. Next Create an instance of AVPlayer and in viewDidLoad initialize it with the previewUrl and when the user click on play call the player.play() method.
You might be wondering as to why we have the IBOutlets for the the button.s If you look at any music app the state of the play button changes when we click on the play button . It changes from play to pause . We can have similar functionality by maintaining a state in the isPlaying variable and set the button image to either play or pause accordingly.
We have the next and prev button which will help in going to the next and previous song. But as you might notice in the MusicDetailViewController we only pass a single musicTrack and not the whole array . Lets look at how we can solve this issue.
Delegation and Protocols
One of the common design pattern you see in most iOS application is Delegation and Protocols. If you look at the UITableView we have `UITableViewDelegate`, CLLocationManager has `CLLocationManagerDelegate`. You can think of delegation as the well know callback mechanism which exists in most other programming languages. Protocol on the other hand defines a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. Protocols are similar to interfaces in Java.
We could do something similar by creating a MusicDetailViewControllerDelegate protocol with 2 function getNext and getPrev . Once we have defined this protocol we need to implement this protocol which in our case will be implemented by ListViewController as it has the whole array and can provide with the next and prev method.
There are a couple of changes which we need to do in the ListViewController file . We need to add functionality to set the musicTrack when someone clicks on the tableviewCell. We will implement the method prepareForSegue and if the identifier is equal to "detail" then we get the destination view controller and set the musicTrack object . Since we have access to the DetailViewController we will also set the delegate to self and implement the getPrev and getNext function. These steps should be done in the ListViewController.swift.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "detail"){
let indexPath = self.tableView.indexPathForCell(sender as! MusicCell)
let tmpTrack = self.musicTracks\[indexPath!.row\]
print(tmpTrack.thumbnail)
print(tmpTrack.artworkUrl)
index = indexPath!.row
let dvc = segue.destinationViewController as! MusicDetailViewController
dvc.delegate = self
dvc.musicTrack = tmpTrack
}
}
func getNext() -> MusicTrack {
index++;
if(index > self.musicTracks.count){
index = 0
}
print("Get Next")
return self.musicTracks\[index\]
}
func getPrev() -> MusicTrack {
index--;
if(index < 0){
index = self.musicTracks.count - 1
}
print("Get Prev")
return self.musicTracks\[index\]
}
This is the final code for the MusicDetailViewController.
//
// MusicDetailViewController.swift
// MusicApp
//
// Created by Shrikar Archak on 6/20/15.
// Copyright © 2015 Shrikar Archak. All rights reserved.
//
import UIKit
import AVFoundation
import Spring
protocol MusicDetailViewControllerDelegate {
func getNext() -> MusicTrack
func getPrev() -> MusicTrack
}
class MusicDetailViewController: UIViewController {
var delegate : MusicDetailViewControllerDelegate?
var musicTrack: MusicTrack!
@IBOutlet weak var bkImageView: UIImageView!
@IBOutlet weak var prevButton: UIButton!
@IBOutlet weak var nextButton: UIButton!
@IBOutlet weak var playButton: UIButton!
@IBOutlet weak var posterImageView: UIImageView!
var isPlaying : Bool = false
var player:AVPlayer?
override func viewDidLoad() {
super.viewDidLoad()
load()
}
func load(){
let imagedata = NSData(contentsOfURL: NSURL(string: musicTrack.artworkUrl)!)
if let tmpdata = imagedata {
bkImageView.image = UIImage(data: tmpdata)
posterImageView.image = UIImage(data: tmpdata)
}
player = AVPlayer(URL: NSURL(string: musicTrack.previewUrl)!)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
@IBAction func play(sender: AnyObject) {
isPlaying = !isPlaying
if(isPlaying){
self.playButton.setImage(UIImage(named: "pause"), forState: UIControlState.Normal)
player?.play()
} else {
self.playButton.setImage(UIImage(named: "play"), forState: UIControlState.Normal)
player?.pause()
}
}
@IBAction func next(sender: AnyObject) {
self.musicTrack = delegate?.getNext()
load()
if(isPlaying){
self.player?.play()
}
}
@IBAction func prev(sender: AnyObject) {
self.musicTrack = delegate?.getPrev()
load()
if(isPlaying){
self.player?.play()
}
}
}
Designing the app:
I am not a designer so don't have much to say about the colors to use in the app. Feel free to use any colors for the background , navbar etc.
Note: I am facing this issue where is some of the cells are not displaying properly on iphone 6 but sometime the same thing is working fine. Please let me know if you are facing the same issue. Also please comment if you are able to resolve this issue.
This is how the detailview controller looked like in my app.