Episode #407

Player Bar Part 1

Series: Making a Podcast App From Scratch

21 minutes
Published on August 29, 2019

This video is only available to subscribers. Get access to this video and 572 others.

We have a player but there's currently no way to bring it back up after dismissing it. In this episode we'll design a persistent player bar that will control the player and will be allowed to live outside its view controller.

Designing the Player Bar

We will start by creating a subclass for the player bar. In the storyboard we will create a view and drag it under the "Player View Controller Scene", this will create a reference. We will set the width and height of the view based on the screen width.

Next, we will add an image view, and a button to open up the player. To enable the voice over functionality we will set the accessibility label on the button.

Next we will add a skip back, skip forward and play/pause buttons along with the image and open up button in a stack view, embedded them in a view and update their constraints.

UIView Settings Of Player Bar

We will now create the two outlets for the image view and Play/Pause button. We will set the rounded corners to the image view by setting the cornerRadius and clipToBounds properties. Also to make the view available in the entire application we will install the player view on the top of the tab bar and set its background color to clear.

class PlayerBar : UIView {

    @IBOutlet weak var imageView: UIImageView!
    @IBOutlet weak var playPauseButton: UIButton!

    override func awakeFromNib() {
        super.awakeFromNib()

        imageView.layer.cornerRadius = 4
        imageView.clipsToBounds = true

        backgroundColor = .clear
    }
}

Adding Separator Below the Player bar

Next, we will draw a 1-pixel border at the bottom of the player bar, this will separate our player bar.

override func draw(_ rect: CGRect) {
    guard let context = UIGraphicsGetCurrentContext() else { return }
    Theme.Colors.gray3.setStroke()

    let lineWidth: CGFloat = 1
    context.setLineWidth(lineWidth)

    let y = rect.height - lineWidth/2
    context.move(to: CGPoint(x: 0, y: y))
    context.addLine(to: CGPoint(x: rect.width, y: y))
    context.strokePath()
}

Connecting the Player Bar and Player View Controller.

We will create an outlet with a strong reference instance of the player bar in PlayerViewController,

@IBOutlet var playerBar: PlayerBar!

As the player will be empty initially, we will hide the player bar in viewDidLoad.

playerBar.isHidden = true

We will now maintain the same state of play/pause button in the view controller and the player bar.

@IBAction func playPause(_ sender: Any) {
    let wasPlaying = playPauseButton.isSelected
    togglePlayPauseButton(isPlaying: !wasPlaying)

    if wasPlaying {
        player?.pause()
    } else {
        player?.play()
    }
}

To maintain the consistency between the play/pause button in the player view controller and player bar, we will set image and image view of the play/pause button in the player bar with similar settings used in playerViewController.

playerBar.playPauseButton.setImage(tintedImage, for: [.selected, .highlighted])
playerBar.imageView.kf.setImage(with: podcast.artworkURL)

Also, we will set the toggle function while we set the episode in the controller.

private func togglePlayPauseButton(isPlaying: Bool) {
    playPauseButton.isSelected = isPlaying
    playerBar.playPauseButton.isSelected = isPlaying
}

Next we will set the visibility of the player bar just before we start playing the player bar. We use asyncAfter 1 second here to allow the player to be presented first before we show the player bar. Doing so eliminates a distracting flash of UI during the player view controller presentation.

DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
    self.playerBar.isHidden = false
}

Finally, we need to have a reference to some view controller that we can present from, since the player bar button can be tapped from anywhere. We will create an optional weak reference of UIViewController to set the state of the player bar in other application. We will then hook up the open player button to the presentPlayer action in playerViewController using our storyboard.

@IBAction func presentPlayer() {
    presentationRootController?.present(PlayerViewController.shared, animated: true, completion: nil)
}

This episode uses Xcode 10.2.1, Swift 5.0.