How to Make tvOS App

04.04.16   Diego Rebosio

I've noticed that many companies with great content somehow fail at providing commensurately great apps, websites and infrastructure that we as consumers expect today. Beachbody is to me a prime example of the kind of company that needs help with this and for whatever reason has not chosen to go down this road yet. As a P90X3 user, I’m not thrilled that I need to use DVDs and that there isn’t a proper app for my Apple TV.

I decided to take matters into my own hands and build a tvOS app to solve my problem.

Pre-requisites:

  • MP4s for every workout
  • PNGs for every workout
  • Mac with XCode
  • Apple TV. Must be a 4th generation device or better
  • Apple Developer Account
  • Background images (1920x1080)

Getting started on XCode:

Create a blank tvOS App. Select the Tabbed Application.

apple tv1

apple tv2

Your screen should now look like this.

apple tv3

If you’d like you can run the app at this point which will run a skeleton app on the emulator.

apple tv4

Since I have both P90X and P90X3, I’m going to put P90X in the first tab and P90X3 in the second tab which will you will see in my images below, but you may only be working with one.

 

Setting the backdrop image:

Select the main story board file on the left to reveal Interface Builder.

apple tv5

Open-up the first scene to reveal the “View” inside it. This is your top-level view. We’re going to now put a background image inside this. Later we’ll add a gallery to select and play the video we want but for now we’ll just focus on the background image.

apple tv6

Now grab the edges on the top left and bottom-right corners to fit with the entire view controller.

apple tv7apple tv8

Control-drag from the image view to the parent view to reveal the auto-layout menu. Then select leading space, trailing space, vertical spacing to top layout and vertical spacing to bottom layout (shift-click 4 times to select all of them at once). This will make sure that your image takes over the entire background of the view controller even if for some reason a different screen size is used.

apple tv9

 

Repeat this exercise for the second scene.

Now is probably a good time to add the background images to your asset library. Select Assets.xcassets on the navigator on the left. Then drag-drop your background images into this.

apple tv10

Now go back to the main storyboard, select the image of the first scene, and now pick the file name to link to the correct background image. Repeat for the second scene.

apple tv11

Finally, I’m going to rename “First” and “Second” to “P90X” and “P90X3” by selecting the tab bar item of each view controller (aka scene) and changing the Title.

apple tv12At this point you can build and run, and your app should show the two different backdrops. You can swap between each view by pressing the left/right arrows.

apple tv13apple tv14

VideoItem Model Class:

So let’s define a class for what our video model objects will look like. File/New/File/tvOS/Source/Cocoa Touch Class. Name your new class “VideoItem” and make it inherit from NSObject (which you really don’t need) or just select File/New/File/tvOS/Source/Swift File instead.

 

Then type-in the following:

 

//

//  VideoItem.swift

//  P90X

//

//  Created by Diego Rebosio on 3/6/16.

//  Copyright © 2016 Diego Rebosio. All rights reserved.

//

import UIKit

class VideoItem {

    var thumbNail : String

    var videoAsset : String

    var title : String

   

    init (title : String, thumbNail: String, videoAsset : String) {

        self.thumbNail = thumbNail

        self.videoAsset = videoAsset

        self.title = title

    }

}

Our class will be pretty simple. It’ll just hold a title for the workout, a thumbnail file name and a video asset file name. We specifically break this out into its own class in case we want to expand things in the future and to keep it all clean, but for the purposes of this demo, this class simply holds those 3 fields. We also create a simple constructor so that it’s easy to instantiate new Video Items and we have no null fields.

Overriding First and Second Scene View Controller Logic.

Our two view controllers are really going to be doing the same thing, so I think it’s a good idea to combine them into one class. In brief, we’re going to create a UICollectionView where we will show the videos, allow the user to flip around to find the video they want, and then press on the remote to view the video.

Let’s make a new class and call it VideoSelectionViewController and have it inherit from UIViewController. Then add UICollectionViewDataSource after UIViewController to make it follow that protocol and essentially be able to provide data to the collection view once we create it.

apple tv15apple tv16

apple tv17

Now add the following code to make sure that our class is doing the bare minimum to comply with the new protocol:

    func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {

        return 1

    }

    func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

        return 0

    }

    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("VideoItemCell", forIndexPath: indexPath)

        return cell

    }

apple tv18

Finally, let’s bind our scenes to the correct class. Go back to the main storyboard, select the scene and change the class:

apple tv19

You can also delete the old classes since you no longer need them.

apple tv20

Asset Import

It’s probably a good time now to drag-in your remaining assets.

Go to your Assets.xcassets catalog and drag-in all image assets.

apple tv21

Now let’s add-in your video assets. You can place them anywhere in your tree but I personally put mine inside folders to prevent clutter.

apple tv22

Finally, let’s create our model (sorry this is hardcoded since this is just a demo app). Go back to your VideoSelectorViewController class and we’re essentially going to populate a new array optional (array of VideoItems) with the right video list according to whether we are looking at P90X or P90X3 (we find which we are from the tab bar item where we are naming the screen).

Obviously the right video items will have to match the workout program (or video library) that you are creating.

apple tv23

You may want to build and run to make sure nothing is broken, but there will be no difference yet in how things run since we haven’t added the collection view to our story board.

Adding the Collection View:

Go back to the storyboard, and now add a collection view across the bottom. Drag it to take the entire width of the screen. The height is basically up to you. Set the background color to something that makes sense visually.

apple tv24

 

Set auto-layout to keep the collection view in place:

apple tv25

Scroll left to be able to see the first view cell and resize it to match the height of your band and with a width proportional to your video images (in my case 1920x1080 is the right proportions so I did 229x129). Also pick an identifier that matches what you entered in collectionView:cellForItemAtIndexPath (“VideoItemCell”).

apple tv26

Control-drag from the collection view to the view controller (P90X in yellow) to connect the parent view controller as the data source of the collection view. This is the glue that makes the methods be called in your view controller when the collection view is about to display.

apple tv27

Let’s now add an image and a title.

First add an image view into the collectionview template cell, and do the drill with auto-layout to ensure it stretches to all the edges.

apple tv28

This time, however, let’s also number the view. This will help us find the imageview easily when generating our collection view cell. Let’s start at the beginning with number 1. Make sure you are doing this on the ImageView, not on another view.

Similarly, let’s add a UILabel and set the tag value to 2. Pick a nice font size color and background color for the label while you’re at it.

apple tv29

Repeat all the steps above for the other view controller.

Now edit your UICollectionViewDataSource methods to provide the right information to the collection view.

In particular, change numberOfItemsInSection to return videoItems!.count and cellForItemAtIndexPath to populate the image and label for each video.

apple tv30

If you build and run now it should start coming to life.

apple tv31

Player

So let’s now create a stock video player. Simply drag one into the storyboard. Subsequently create a segue from each of the two view controllers into it. (ctrl-drag from each of the initial view controllers to the player view controller).

apple tv32

 

apple tv33

 

Our new player can only work with a concrete class that inherits from AVPlayerViewController so let’s make a new class file now named P90XPlayerViewController. Make sure that the class for the player matches your new class.

apple tv34

CollectionViewDelegate

So now let’s implement the CollectionViewDelegate. This is the part that will allow us to select an actual video, and kick-off the segue to start the video player once an item is selected.

Start in the storyboard by control-clicking form each of the two collection views into their parent view controllers and selecting “delegate” as an outlet. Make sure you do this in both of them.

apple tv35

Now add UICollectionViewDelegate to the list of protocols that VideoSelectorViewController implements:

 

class VideoSelectorViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {

 

Let’s now implement collectionView:didSelectItemAtIndexPath (note a common mistake is to select didDeselect instead…)

 

    func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) {

        switch self.tabBarItem.title! {

        case "P90X":

            self.performSegueWithIdentifier("PlayP90XVideo", sender: self)

        case "P90X3":

            self.performSegueWithIdentifier("PlayP90X3Video", sender: self)

        default:

            print ("This line should never run")

        }

    }

 

At this point you can build and run and your new player will open up but we have a couple big missing items:

  1. It’s hard to see what you’ve actually selected and
  2. The player opens but never plays anything (we need to connect the video).

 

Setting the Focus

This is one of the things that’s a little different with tvOS. Some times you need to specifically note that items can obtain “focus”. There’s actually quite a bit of documentation on this. Our problem, however is slightly different. Our collection view items DO get the focus, the problem is that there are no visual cues to show that something’s been selected. To show this, simply add the following into the cellForItemAtIndexPath method:

        imageView.adjustsImageWhenAncestorFocused = true

This makes the image change its look to visually show that focus has now changed.

 

Feel free to build and run to show this now.

 

Playing the Video.

So now let’s go back to our new P90XPlayerViewController class.

 

Let’s simply add some logic to play the video that we are in when the view loads.

 

//

//  P90XPlayerViewController.swift

//  P90X Rocks

//

//  Created by Diego Rebosio on 3/9/16.

//  Copyright © 2016 Diego Rebosio. All rights reserved.

//

 

import UIKit

import AVKit

 

class P90XPlayerViewController: AVPlayerViewController {

   

    var videoItem : VideoItem?

 

    override func viewDidLoad() {

        super.viewDidLoad()

        playVideo()

       

        // Do any additional setup after loading the view.

    }

   

    func playVideo () {

        var fileURL = NSBundle.mainBundle().URLForResource(videoItem?.videoAsset, withExtension: "mp4")

        if fileURL == nil {

            fileURL = NSBundle.mainBundle().URLForResource(videoItem?.videoAsset, withExtension: "m4v")

        }

        player = AVPlayer(URL: fileURL!)

        player?.play()

    }

 

}

 

We need, however, to now populate our new videoItem variable when we kick-off the segue, so let’s go back to the VideoSelectorViewController class and implement performSegueWithIdentifier:

    var currentIndexPath : NSIndexPath?

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

        let playerVC = segue.destinationViewController as! P90XPlayerViewController

       

        playerVC.videoItem = videoItems![currentIndexPath!.row]

    }

   

 Note that this method uses a new attribute currentIndexPath and so we should set this in didSelectItemAtIndexPath:

 

        currentIndexPath = indexPath

 

 

Wrap-up

At this point everything should work now. Your final step is to attach an actual tvOS device which you can select from your device selector up on top. I don’t have one with me right this second (I’m writing this post at work and my AppleTV is at home) but you can pick it from this list, and if you are a registered developer, you will be able to install to the device at this time. It does take a few minutes the first time you do this (lots of Gbs of video to install).

 

The only other missing item is an actual icon for your new app. I’ll leave that exercise to you.

 

Enjoy!

apple tv36

P.S. If you actually work at Beachbody, or a similar company with amazing content, feel free to ping us, we’ll be glad to get you on the right path to making your amazing content be delivered in a better way.