Table of contents

Hello kids, today we will talk about the basics you need to know when switching from iOS development to tvOS. Let’s get straight to it.

Let’s start with the important thing to know. How you develop. There are 2 ways you can use here:

1. Using TVML library.

If you come across a legacy code for some reason, take a look at linked libraries. Do you see there something with TVML prefix? If yes, well, I know that feeling, boi.

The thing is that this kit allows mixing JS code with native. Native code is used for setting up UI, datasources, delegates etc and sending events to JS part. And the JS part handles navigation logic. Each time any of the js code changes you will need to recompile it and generate application.js file, which is basically main js file. Also it can be stored on the external server. What’s good about that? You can change user flow without sending an app for another review and for Native-Code-Guy it is the one and only (song reference) advantage. And every time I even hear about it:

iOS to tvOS

2. The native way.

Well, it’s the native way, there’s nothing too terrifying …I guess.

Controls

The first thing that comes to mind is how user interacts with the device. As Captain Obvious would say: “Using remote, boi.” And you know what? He’s absolutely 100% right. In iOS you would use method:

func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?)

to catch the touch, but here you should be using

func pressesBegan(_ presses: Set<UIPress>, with event: UIPressesEvent?)`

As UIPress has property type which is enum of UIPressType. You can use it to see when user presses menu or play/pause or select buttons. Apple requires your app to return to the main screen on Menu button press when it makes sense, usually it’s home screen of your app.

Limitations

Basically, there are 3 of them:
1. The initial size of your app can’t exceed 4GB.

This storage should be enough for most of us. However, just in case, there’s such thing as “On-Demand Resources”, so you can download other resources when they are needed, like new level for the game or some other examples which refused to visit my head while I was writing this.

Oh yeah, one more thing about On-Demand Resources, might be important for someone. They are not “super guaranteed” to be there the next time user loads the app/game, because Apple TV might use this storage and delete your resources, for example. So you might need to download missing resources again.

2. You can not use device local storage.

Well, technically, you can, but just like in the previous example, stored data is not “super guaranteed” to be there next time user loads the app/game.

So for storing progress, info, whatever, you should use iCloud or your own backend.

3. No Web View.

Apple wants us to create fully native experience, so everything you wanted to display in the webview has to be adapted to the native views.

Focus Engine

Because there is no touch screen (hey, Captain Obvious), tvOS uses Focus Engine to give users some tips where they are and what they’re going to interact with. When an element is in focus, it’s basically increasing in size and changing color, but it’s enough for user to understand what’s going on.

Elements like UIButton, UITextField, UITableView, UICollectionView, UITextView, UISegmentControl and UISearchBar are focusable by default. For your own views which you want to be in focus, you will have to implement custom behavior, mostly items with non-rectangular shape will require this. All focusable elements should conform to protocol: UIFocusEnviroment. Here is main interface that we are going to use for implementing custom focusing views:

var canBecomeFocused: Bool // pretty obvious right

// Damn I love word obvious

func setNeedsFocusUpdate() -> Void // requests focus engine
to update focus in current environment c.o

var preferredFocusEnviroments: [UIFocusEnviroment] //
An array of objects that conform to protocol UIFocusEnviroment,
which current environment will prefer to focus on, IN PRIORITY ORDER.

func shouldUpdateFocus(in context: UIFocusUpdateContext) ->
Bool // c.o

func didUpdateFocus(in context: UIFocusUpdateContext,
with coordinator: UIFocusAnimationCoordinator) //
pretty obvious, but want to add that this method is
important because it provides coordinator to us,
which can be used to add focusing/defocusing animations
right after system animations.

Also, in the final part I would like to mention the UIImageView property adjustsImageWhenAncestorFocused. It gives your image same effect as AppIcons image have, it’s called Parallax Effect and it looks pretty nice. So if you want to save some time and your image is not rounded or has a non-rectangular shape you can just make a snapshot of the view and use it.
Just do not forget to set value of mastToBound and clipToBoundsproperties to false .

There is some good description of it in articles like this or this, and I’m not going to reinvent the wheel by retelling you the same story. Buuut, just in case, I’ve made a quick example of how you can easily apply Parallax Effect to your views, it’s available here. Most interesting files for you will be CollectionCell.swift and Extension.swift. Feel free to download it and use the code from there.