Apple TV tvOS hello world app in Swift
Apple TV TVML tvOS hello world app in Swift
Introduction
Table of Contents
So, you saw the shiny new AppleTV demo on the Apple Live Event. Finally we can write apps for the beast! Like most of you, I downloaded the Xcode 7.1 beta to jump in. Hey, there’s a project template! Let’s try that. Oh, that’s it? Looks like any other iOS app. Where’ the TV code? Sigh. I guess I’ll have to RTFM.
There are two types of app. One relies on “templates” written in XML called TVML (Television markup language). The other “low level” way is to write custom apps in Swift (or objc). Here is a list of iOS APIs that did or did not make it to tvOS. (Interesting that AudioToolbox made it but CoreMIDI didn’t – and no mention of Core Audio). In this post, I’ll talk about the TVML approach.
Apple has provided us with their usual almost-adequate tvOS documentation. After you get an idea of how it works, you get a “hello world” type page named Creating a Client-Server App.
Cool. The sample code is not downloadable, so you need to scrape the code off the page and fix the problems.
Not cool.
Here’s my attempt at being a bit more helpful.
Getting started
Go ahead and read Creating a Client-Server App. This will give you a good conceptual overview. I’ll give you action items below.
Server
Let’s start with the server side. For development, you will need to serve JavaScript and TV Markup (TVML) files. There are various ways to do this. You can use Python, but I don’t like snakes or syntactic whitespace (both will bite you). Since you’re hip and happenin’ you probably have Node.js installed. So, let’s bask in your grooviosity and use Node to serve your files.
You will need to install http-server via npm.
1 |
npm install http-server -g |
Easy.
Then to serve the files in a subdirectory named “public” you simply type
1 |
http-server |
Or if your files are in a different directory
1 |
http-server differentDirectory. |
So, what’s in that directory?
Go ahead and create a tvOS “Single View Application” project. Now drop to a terminal and create a directory named public under your project. Then import that directory to your project. Wouldn’t it be nice if you could do this directly in Xcode?
Now, you need two files (to start) in this directory. First, the JavaScript. I created a file named tv.js. Name it what you’d like. Here is Apple’s code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
function getDocument(url) { var templateXHR = new XMLHttpRequest(); templateXHR.responseType = "document"; templateXHR.addEventListener("load", function() {pushDoc(templateXHR.responseXML);}, false); templateXHR.open("GET", url, true); templateXHR.send(); return templateXHR; } function pushDoc(document) { navigationDocument.pushDocument(document); } App.onLaunch = function(options) { // This is the change: var templateURL = 'http://localhost:8080/yo.tvml'; getDocument(templateURL); } App.onExit = function() { console.log('App finished'); } |
Don’t bother copying it, I’ll point you to a Github repo later.
The other file you need is a TVML file. Note that in the onLaunch function I referenced a file named yo.tvml. Go ahead and create it in the public directory.
1 |
Yo, tvOS! |
This is Apple’s minimal example. In my next blog post, I’ll go into more details, but if you want more now, read about Templates.
In the terminal, start the http-server. In another terminal (or tab if you use iTerm) you can test it with curl. Or use your browser.
1 2 |
curl http://localhost:8080/yo.tvml curl http://localhost:8080/tv.js |
Yay. You’re serving if you see the file’s contents.
Swift
Make these modifications to your app delegate. This is how your app finds the JavaScript you’re serving. If you named your JavaScript file differently, modify the name here.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class AppDelegate: UIResponder, UIApplicationDelegate, TVApplicationControllerDelegate { var window: UIWindow? var appController: TVApplicationController? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { self.window = UIWindow(frame:UIScreen.mainScreen().bounds) let appControllerContext = TVApplicationControllerContext() if let javaScriptApplicationURL = NSURL(string: "http://localhost:8080/tv.js") { appControllerContext.javaScriptApplicationURL = javaScriptApplicationURL print("set javaScriptApplicationURL to \(javaScriptApplicationURL)") } if let options = launchOptions { for (kind, value) in options { if let kindStr = kind as? String { appControllerContext.launchOptions[kindStr] = value } } } self.appController = TVApplicationController(context: appControllerContext, window: self.window, delegate: self) return true } |
That’s it for your Swift code.
Security
If you run your app now, it will crash with a security problem. Apple says read the
App Transport Security Technote.
Here’s the tl;dr.
Open your Info.plist as source code and add this key.
1 2 3 |
NSAppTransportSecurity NSAllowsArbitraryLoads |
While you’re in Info.plist, delete the storyboard reference. You won’t be using a storyboard. You can delete the ViewController.swift file too (as specified in Apple’s documentation).
Run it now.
That complicated TVML file we served looks like this:
Summary
tvOS TVML apps use a client-server architecture. You need to serve a JavaScript file and TVML files from a web server. Your Swift code will reference this JavaScript file. Most of your UI will be written in TVML.
Update
Since I wrote this, Apple has published some sample code that you can download as a project. Yay.
Here is their TVML example. As usual it’s a dog’s dinner rather than a tutorial.
Hi Gene,
Pardon the post on your blog, I’m not sure how to contact you and feel you’re probably the only guy that may know how to help me based on your great MIDI postings. I’ve posted on SO but have no response after several days and this is killing the momentum of my app development.
The SO question I’ve posed is here : http://stackoverflow.com/questions/32948329/how-to-correctly-configure-a-chain-of-avaudiouniteffects
What you’ll see is I’m trying to connect up an AVAudioUnitSample to a change of effects distortion -> delay -> reverb. It seems to all connect up but as soon as I play a MIDI note I get an error -10867 and have no idea why.
I’d be so grateful if you could enlighten me please.
Warm regards,
Chris