作者:Kelvin Lau 原文地址:Beginning tvOS Development with TVML Tutorial
在2015年9月9日的产品发布会中,Apple宣布了新一代的Apple TV以及tvOS,并且在tvOS中集成了App Store。这使得我们多年以来想在Apple TV上开发专属应用的梦想成真了。
我和其他教程团队的成员已经开始深入研究tvOS SDK,并且正在努力为你们准备一些有价值的tvOS教程。在你开这篇文章之前,Chris Wagner已经写了一篇关于tvOS初步印象的文章,我也基于这篇文章,设计了第一个tvOS的教程。
译者注:可参阅Chris Wagner文章的中译版一个iOS开发者对tvOS SDK的初探。
在这篇教程中,你将会使用TVML开发你的第一款tvOS应用。信不信由你,你将会使用JavaScript管理你应用的逻辑以及创建TVML模板来展现你应用的UI。
当教程结束后,你应该可以基本理解如果通过TVML和TVJS管理、控制tvOS应用。现在就我们开始吧。
注意:该教程需要Xcode7.1或更高的版本,你们可以在这里下载。虽然你们可以跟着该教程一步一步的进行操作,但我还是建议你们能储备一些基本的JavaScript知识。
选择开发方式
Apple为开发tvOS应用提供了两种方式:
- TVML Apps:这类应用是使用完整的新开发技术开发的,比如TVML、TVJS、TVMLKit。在稍后我会解释这些简称的含义以及如何使用它们。
- Custom Apps:这类应用是使用我们已经比较熟悉的开发技术进行开发的,比如大家熟知的一些iOS框架和特性,像Storyboard、UIKit、Auto Layout等。
这两种方式没有孰优孰劣之分,都是Apple推荐的方法,只是按需所取,以及你更想尝试哪种方式。
在这篇教程中,你们的目标是开发以个能播放RWDevCon讨论视频的tvOS应用:
虽然用上述两种方式都可以开发这个应用,但是使用TVML会更加容易一些。所以这就是你在这篇教程中要学和要做的东西。
什么是TVML?
正如我刚才提到的,第一种开发tvOS应用的方式是通过TVML、TVJS和TVMLKit这些新的技术实现的。如果你们对这些简称比较陌生,不要惊慌,因为他们本就是新鲜玩意。这里我简单解释一下:
- TVML是一种XML格式,基于“Television Markup Language”。
- TVJS是基于JavaScript APIs的脚本语言,它可以根据TVML中定义的内容展示应用。
- TVMLKit是连接TVML、TVJS和原生tvOS应用的工具包。
如果你是一名经常使用原生API开发原生iOS应用的开发者,那么看到这些类似前端的技术可能会有点退缩。但希望你能保持一颗学习的心,学习了解这些新的强大的特性。
我在这列举一个非常典型的Apple TV的应用场景。大家可以想象一下:你们想把一些内容或信息展现给用户,这些内容和信息储存在你们的服务器上,并且这些内容的格式、查询方式都服务于iPhone或iPad中的应用,那么你肯定会希望你的tvOS中的应用也能方便的使用这些内容,并做到与iOS中应用有相似的展现、查询、导航方式。
上述的这个场景恰恰是我们这片教程中的场景。我们已经有RWDevCon网站,上面有许多技术讨论视频,所以运用TVML模板应该很容易实现。并且我们也没有很严格的用户界面的需求,所以我们可以简单方便的使用Apple提供的UI模板。
简而言之:
- 开发TVML App:如果你主要是通过tvOS应用展现一些内容,不论是音频、视频、文本、图片,并且你已经有服务器存储这些资源。那么使用TVML开发是不错的选择。
- 开发Custom App:如果你希望用户不只是被动的通过你的tvOS应用观看或收听内容,而是希望用户与应用有更多的交互,给用户高质量的用户体验。那么你应该选择使用iOS的相关技术开发自定义的应用。
现在你们已经大概了解了TVML是如何工作的,以及我们为什么要在这篇教程中使用TVML开发tvOS应用。想要更深入的了解,最好的办法就是由你们在实践中去学习、理解了。让我们开始动手吧!
准备工作
首先你们要确保已经下载并安装了Xcode7.1或更高版本。
然后通过 File\New\Project 创建新工程,在侧边栏选择 tvOS\Application\Single View Application 模板,然后点击 Next:
项目名称输入 RWDevCon ,语言选择 Swift ,确保下面的两个复选框为未选中状态,也就是不使用Core Data和单元测试,然后点击 Next:
选择一个目录,点击 Save 保存你的项目。Xcode会为你创建一个带有Storyboard的空工程(如果你开发自定义UI的tvOS应用,那么你需要使用Storyboard)。
然而在该教程中你不需要使用Storybard,因为我们会使用TVML来展示应用的UI,而不是用Storybard去设计UI。所以将 Main.storyboard 和 ViewController.swift 删去,在提示框中选择 Move To Trash 彻底删除。
接着打开 Info.plist 文件,删掉Main storybaord file base name
属性。最后添加新的属性App Transport Security Settings
(区分大小写),以及它的子属性Allow Arbitrary Loads
,并将其值设为YES
。
注意:在iOS9中,Apple不允许应用链接非HHTPS协议的服务,所以刚才的操作是很有必要的,因为在该教程中,你们将会以HTTP协议访问本地的服务器,所以你需要在Info.plist中添加上述属性以便允许应用通过HTTP协议访问服务器。
加载你的TVML
tvOS应用的生命周期开始于AppDelegate。在这里,你将创建TVApplicationController
以及应用上下文,并将它们传给主要的JavaScript文件。
打开AppDelegate.swift并做下面这些事:
- 删除所有的方法。
- 导入
TVMLKit
。 - 使AppDelegate遵循
TVApplicationControllerDelegate
协议。
当完成这些事后,你的AppDelegate.swift看起来应该像这样:
|
接着添加下面这些属性:
|
TVApplicationController
是TVMLKit
中的一个类,它负责与你的服务器的交互。TVBaseURL
和TVBootURL
包含了你的服务器的地址和JavaScript文件的地址,该JavaScript文件稍后会运行在你的服务器中。
接在在AppDelegate中添加如下方法:
|
这些代码相对还是比较容易理解的:
- 这里你首先创建了一个应用上下文
TVApplicationControllerContext
的实例,用于稍后初始化你的TVApplicationController
。你可以理解为给一个简单的对象设置了一些属性,比如服务器的URL,然后该对象又作为属性设置给了另一个对象。 - 给应用上下文这个对象实例设置了两个简单的属性:主JavaScript文件的路径和服务器的地址。
- 通过你刚才设置好的应用上下文初始化
TVApplicationController
。此时就完全由Apple代码来接管了,他会加载到你的主JavaScript文件,并开始执行其内容。
所以到目前为止,是时候让Xcode休息一会了,因为接下来你们将要编写JavaScript了。
The JavaScript
在客户端-服务端这类的tvOS应用中,你的JavaScript文件通常在应用连接的服务器中。在该教程中,你们将会在Mac上搭建一个简单的服务器。
客户端代码
为了方便起见,我们把JavaScript文件放在桌面,在你们的 桌面 文件夹中新建一个文件夹名为 client 。在client文件夹中再新建一个文件夹名为 js 。该文件夹将作为你的JavaScript文件的容器。
通过你使用的编辑JavaScript的IDE,新建一个JavaScript文件,名为 application.js ,将它保存在你刚才新建的 js 文件夹中。然后在 application.js 中添加如下代码:
|
App.onLaunch
是处理JavaScript文件的入口方法。之前在 AppDelegate.swift 中已经初始化好的TVApplicationController
会将TVApplicationControllerContext
传到这。之后你会使用到上下文中的内容,但是现在,我们只创建一个简单的提示界面并显示在屏幕上。
- 通过下面定义的
createAlert
函数,我们获得到了为我们展现界面的TVML文件。navigationDocument
类似于iOS中的UINavigationController
,它提供像栈一样的方式,可以推出或压进展现界面的TVML文件。 createAlert
是一个返回TVML文件的函数,你可以将它看作类似iOS中的UIAlertController
。
写到这顺便提一下,Apple已经提供了18种TVML模板供我们使用,你们可以在该Apple TV Markup Language Reference中查阅完成的模板列表。
上述代码中的 alertTemplate 就是这18个模板中的其中一个,它的主要用于展示重要信息,比如通过一段消息提示用户在继续操作之前需要执行其他的操作等。此时,距离你们编译运行你们的第一个tvOS应用已为时不远了。
配置服务器
打开 Terminal 输入如下命令:
|
这两行命令的作用是在先前创建的client目录中开启一个基于Python的web服务器。现在,你们可以准备起飞了!
回到你的Xcode项目中编译运行程序。你应该可以看到你的第一个tvOS TVML应用了!
我不知道你们的感觉如何,但是当我第一次运行成功后,我的感受就像下面这个家伙一样:
在继续进行教程之前,我想花点时间对你们目前已经完成的工作作以总结:
- 你们创建了
TVApplicationController
实例。它用于管理JavaScript代码。 - 你们创建了
TVApplicationControllerContext
实例,并在创建TVApplicationController
时将其与之关联。应用上下文有一个launchOption
属性,用来构建我们的BASEURL
,也就是服务器的地址。该应用上下文也用于配置tvOS应用与哪个服务器连接。 - 控制器被传到了JavaScript代码中。
App.onLaunch
作为整个JavaScript文件的入口方法,你们定义了createAlert
函数,返回TVML提示信息模板文件,并由navigationDocument
管理并展现界面。最后将“Hello World”显示在屏幕上。
即使现在你们使用的服务器是运行在本机的,但是你们仍然可以连接一个真实的远程的服务器,可能是一个连着数据库的服务器。你们感受并想象一下应用场景,应该会很酷,对吧?
完善TVML模板
我之前提到过,createAlert
是一个返回TVML模板文件的函数。有很多属性可由我们在TVML文件中编辑修改,作为一个实验性质的小例子,你们将会在当前的 alertTemplate 中添加一个按钮。回到你们的JavaScript代码中,将目光聚焦在createAlert
函数上,在模板中添加一个按钮:
|
这里解释一下上述代码:
- 一个TVML文件的第一级标签是
<document>
,也就是整个模板内容是由<document>
和</document>
包起来的。 - 接着你们开始定义模板。使用Apple提供的 alertTemplate 模板,通过
createAlert
函数将其返回。 - 在该模板里,根据Apple的Apple TV Markup Language Reference文档规范,添加了按钮、标题、描述三个标签。
保存你们刚才编辑的JavaScript文件,再次编译运行。你们看到在提示信息下面出现了一个按钮。瞧,TVML是不是将构建tvOS UI变得很简单!
注意:在一个模板中,你能添加的元素数量和类型基于这个的模板的类型。比如,一个 loading Template 就不允许有任何按钮出现。此外,你可以自定义字体、颜色和其他一些属性。但是这些知识已经超越了该教程的范畴。你们可以查阅Apple TV Markup Language Reference文档去了解更多TVML模板的信息。
丰富JavaScript客户端
到目前为止,你们已经完成了一些工作,并且正按照我们的指引一步一步达成目标。在这一节中,你们将要花一点时间在不同的JavaScript文件中将一些逻辑抽象出来,便于能更好的重用。
在 client/js 文件夹中新建一个JavaScript文件,名为 Presenter.js 。在该文件中,你们将定义Presenter
类用于处理导航各个界面,或者说各个TVML模板文件,并且处理事件响应。在 Presenter.js 中添加如下代码:
|
让我们解释一下上述代码:
- 还记得你们在之前
createAlert
函数中用过的DOMParser
类么,它可以将TVML字符串转换为可用于展示的TVML模板对象。因为该类不需要多次创建实例,所以采用单例模式创建它。然后通过DOMParser
的parseFormString()
方法将TVML字符串转为模板对象。 modalDialogPresenter
方法通过传入的TVML模板文件,将其模态的展现在屏幕上。pushDocument
方法是在导航栈中推送一个TVML模板文件,相当于在iOS中push出一个界面。
在之后,你们还会用到Presenter
类管理选中处理操作。现在,让我们使用Presenter
类对之前的JavaScript代码进行重构。将App.onLaunch
中的代码替换为如下代码:
|
这些代码相对还是比较容易理解的,我们来看一下:
- 首先创建一个新的JavaScript文件的数组。然后通过
options
参数获取到BASEURL
属性,并组装Presenter.js
的路径。这里的options
就是之前我们在AppDelegate
类中创建的TVApplicationControllerContext
,BASEURL
自然也是那时我们设置的。 evaluateScripts
将加载JavaScript文件。- 这里,你应该处理异常信息,稍后我们完善这里。
在继续进行之前,编译运行程序,确保JavaScript文件修改过之后程序仍能正常运行。此时,我们通过Presenter
类对JavaScript代码的重构有了一个良好的开端:
现在,看看上面代码中被注释的那行,你们能否自行完成对异常处理的挑战呢。如果evaluateScripts
处理失败,可能是因为JavaScript文件的路径写错了,那么你可能会希望在此时显示一个提示消息给用户。提示: 之所以在这里出现了异常,是因为Presenter
类加载失败导致,所以在这里你不能使用Presenter
类显示提示信息的界面。
你应该通过之前所学到的知识来解决该问题。如果你们觉得有困难,那么可以参照下面的代码:
|
想要测试错误信息,你们可以修改一下JavaScript文件的路径,比如把Presenter.js
改为Presentr.js
:
|
使用CatalogTemplate
catalogTemplate模板同样也是Apple提供的18个模板中的一个。它的作用是以分组的形式展现内容,用它来展示你们最喜欢的RWDevCon视频最好不过了! catalogTemplate有许多有意思的元素:
复合元素和简单元素
该模板中的banner
元素在应用顶部,用于展示应用基本信息,比如名称、标题等。它本身是一个 复合元素 ,也就是说它是由多个 简单元素 组合而成。比如,在banner
中很显然有标题,那么该标题就是一个简单的title
元素,并且在title
背后还有背景图片,这又是另外一个简单元素background
。所以banner
是由两个简单元素组合而成。
让我们来试试这个模板吧。打开 client 文件夹,在 js 文件夹的同级目录新建两个文件夹,分别命名为 images 和 templates 。此时你的 client 文件夹里的内容应该是这样的:
你们会需要图片构建模板中的Cells,在我们这个场景中就是一个一个的视频,图片自然就是视频的封面了。我已经为你们准备好了封面图片,你们可以从这里下载。下载成功后,将他们解压放在刚才你们创建的 images 文件夹中。
现在,你们即将要做的工作是在屏幕中显示图片!新建一个JavaScript文件,命名为 RWDevConTemplate.xml.js ,将其存在 templates 文件夹中。
打开 RWDevConTemplate.xml.js ,添加如下代码:
|
现在,我们试图通过catalogTemplate
模板显示一个Banner条。但在使用只包含模板信息的JavaScript文件之前,我们需要通过某种方法让其他的JavaScript文件知道该文件的存在并能加载其模板信息,因为当前它没有通过任何方式向其他JavaScript文件暴露过。所以我们要创建的最后一个JavaScript文件: ResourceLoader.js 就是用来解决该问题的!
ResourceLoader
新建一个JavaScript文件,命名为 ResourceLoader.js ,保存在 js 文件夹中,和 application.js 、 Presenter.js 一起。打开 ResourceLoader.js 添加如下代码:
|
不用过于担心看不懂这些代码逐行的含义,你们只要清楚这些代码的作用是加载其他模板文件就可以了。
之前我们的主屏显示的是“Hello World”的提示信息模板,现在试着将它换成我们创建的RWDevConTemplate
。打开 application.js 文件,根据如下代码修改之前的代码:
|
此时你们已经对之前的代码进行了三处的修改:
- 申明了一个
resourceLoader
变量。 - 将 ResourceLoader.js 文件添加到JavaScript文件数组中。
- 使用
resourceLoader
加载TVML模板,然后使用Presenter
展现在屏幕上。
编译运行程序,你们应该会看到如下界面:
恭喜你们,现在你们已经可以通过更好的方式从JavaScript文件中加载TVML模板信息了,而不再使用硬编码写死在代码里!
完善catalogTemplate
你管你们信不信,我们要做的tvOS应用马上要接近尾声了。通过TVML开发tvOS应用最优雅的一件事就是添加界面元素非常之简单。
打开 RWDevConTemplate.xml.js 文件,按照如下代码更新之前代码:
|
- 在上面的代码中,新定义了一个list标签,该标签中的内容就是显示在屏幕上除了Banner以外的全部内容。
listItemLockup
代表一个组,它以listItemLockup
标签开头。在该标签中,通过title
标签定义了它的名称“Inspiration Videos”,然后通过decorationLabel
标签定义了该组中包含内容的数量。
编译运行程序,在模拟器中你们会看到如下界面:
看着还不赖吧!
完成catalogTemplate
最后,我们准备在模板中添加cell,用于展示每一个视频。打开 RWDevConTemplate.xml.js 添加如下代码:
|
- 从上述代码中可以看到,在
listItemLockup
标签中添加了relatedContent
,该标签是的作用是显示图中红色圆圈区域的:
- 每个
lockup
代表一个视频,每个该标签中都有videoURL
的属性,它的值就是 RWDevCon 网站上视频的地址。对于之后播放视频至关重要。
编译运行程序,会看到被你赋予新生命力的应用:
现在,我们已经在“Inspiration Videos”这个组里添加了若干视频。让我们打开遥控器的模拟器,选中模拟器,在菜单栏中选择 Hardware\Show Apple TV Remote 。你可以通过遥控器中的 option 键选择不同的视频。
播放视频
到目前为止,我们已经构建好了应用的页面,看起来还是不错的。此时,你们可以再想想如果用iOS框架完成你们现在已经完成的布局,应该如何做。Apple把一些UI的细节全都抽象了出来,通过一个个模板提供给我们使用,可以让我们简单方便的通过模板创建出完美的界面,不得不说Apple做的太棒了。
接下来让我们完成最后两个遗留的功能:选择视频和播放视频。
选择事件
你们可能已经注意到了,当按下 enter 键或者在 Apple TV Remote 选择视频时并没有什么反应,所以是时候来实现选择视频的功能了。
打开Presenter
,添加如下代码:
|
load
函数用来处理视频选择事件。它相当于iOS中的@IBAction
,该函数的event
参数相当于sender
参数。每个event
都有一个target
,每个target
关联着模板中的lockup
元素。一个lockup
代表应用中的一个视频,它里面有视频封面的属性,以及视频地址videoURL
属性。- 播放视频非常简单。
Player
是 TVJS 框架提供的一个类,负责所有视频播放的相关功能。你们所要做的只是添加一个播放列表playlist
,然后将要播放的项目mediaItem
添加到播放列表里。最后通过player.present()
方法就可以播放视频了。
现在你们已经实现了选择视频后的响应事件。是时候将选择事件与每个视频关联在一起了。打开 application.js 文件,在App.onLaunch
方法中添加如下代码:
|
上述代码中的addEventListener
方法相当于iOS中按钮的@IBAction
。编译运行程序,选择一个视频播放,你会看到一个完美的视频播放应用: