3 tips for building with tvOS
Since we already have developers signing up for our Apple TV App Challenge, I wanted to share a few basic strategies that can make for smooth sailing when building with tvOS. While the platform has much in common with iOS, there are some key differences you should be aware of before you dive in.
Tip #1: Configuring the focus engine
tvOS introduced Apple developers to the focus engine, which presents a different user interaction metaphor than iOS developers are used to. Gone are taps, drags, pinches and twists; gestures are replaced by the concept of ‘focus’. Only a single UIView is selected at a time, allowing the user only to navigate directionally to other views and click.
For the most part, the default behavior will serve you well—navigation between views is handled automatically. One can query which view is currently focused by calling [UIScreen mainScreen].focusedView, or check whether an individual view is focused by checking the value of view.focused.
But how can one programmatically set the focus, or customize navigation?
Fortunately, both are easily accomplished by implementing a few simple methods inside your view controller.
The method -(UIView *) preferredFocusView is used to specify the view you wish to be focused when the focus engine updates. This happens both when your controller is initially displayed (i.e., viewWillAppear), as well as when you explicitly call updateFocusIfNeeded.
Apple also provides us -(BOOL) shouldUpdateFocusInContext:, which we can override to specify whether the user’s focus ‘move’ is legal. We’re given references to the view to be deselected, the view to be selected, and the heading (direction) of the motion. A return value of YES signifies that the focus change should be allowed; a return value of NO means that the user’s swipe will be ignored.
To put this all together, consider a simple contrived example with the following constraints:
- Initially, we don’t care what view is selected.
- If some method is called, we want to programmatically focus a custom view named viewToFocus.
- The user is only allowed to swipe right, selecting viewToFocus.
This can be implemented in Objective-C like so:
Of course, you wouldn’t want to implement these exact constraints in a real app, but the above is easily adapted to your specific situation.
Tip #2: How to work without local storage
For better or for worse, tvOS does not allow you much in the way of persistent storage. You can write all you like to the NSTemporaryDirectory via NSFileManager, but so long as your app is not running, tvOS can (and will!) remove files in your app’s container at will. iOS’ trusty NSDocumentsDirectory is not even available. You’re allowed a paltry 500kb of space in NSUserDefaults, but only for NSCoding compliant objects.
Fear not. A number of options are available to you. In the simplest case, embedding resources inside your application bundle may suffice. For larger applications, other options are available: Given that Apple TVs are stationary devices typically connected to home, wireline broadband, you need only think in terms of home Wi-Fi networks; cellular connectivity need not apply. With that in mind, network-backed storage and resources make a lot of sense. Apple, it seems, had the same idea—both iCloud and on-demand resouces, via NSBundleResourceRequest, leverage exactly this scenario.
iCloud storage comes in multiple flavors—the simplest being key-value storage, and the most powerful allowing you SQL-like row-based storage and relational queries. While these require some setup on your part, you benefit from joining the iCloud ecosystem: you’re able to read and write the same data on another iOS device (or even an Apple Watch or a Mac!) so long as the user is signed in with the same Apple ID. The flexibility to grow your app into a seamless, cross-platform service provides added value for this approach.
On-demand resources provide the ability to access remote images, videos, and other static data types only when they’re needed. Because Apple takes responsibility for delivering and storing these resources, you need neither pay for file hosting, nor write any network or file code in your app. Once again, the additional benefits make this approach worth considering.
Tip #3: Share code between Apple platforms
Sharing code between targets has become increasingly interesting in recent years. With the introduction of app extensions, using embedded frameworks have gained popularity as a way to re-use code in multiple modules. tvOS (and indeed watchOS as well) introduces a new challenge: certain properties, methods, and sometimes entire frameworks are unavailable on these targets.
Xcode ships with a very handy header just for this purpose—TargetConditionals.h. Included by default in your projects, this provides #defines for the various operating systems available – TARGET_OS_IOS, TARGET_OS_TV, TARGET_OS_WATCH, et cetera. You can use these to surround your platform-specific code in preprocessor checks to include or exclude entire sections selectively.
To revisit our contrived example for Tip #1, suppose we want to add iOS support to our view controller so that it can be shared across both platforms. (You can even point tvOS and iOS storyboards at the same controller implementation!) Our method changeFocus doesn’t contain any code that is executable under iOS. No worries—target conditionals to the rescue:
Judicious use of target conditionals can allow you to share a large codebase between platforms, without copy/pasting files with minor edits.
These are just some basic things you need to think about to get started with tvOS. But configuring the focus engine, leveraging cloud storage, and repurposing existing code are really just the tip of the iceberg. We’ll post more as time goes on, and we can’t wait to see what you submit to the tvOS Challenge!