Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4370

tvOS Tutorial: Using TVML Templates

$
0
0
Note: This is an abbreviated chapter from the tvOS Apprentice, to give you a sneak peek of what’s inside the book, released as part of the tvOS Apprentice Week. We hope you enjoy!

tvml-templates-feature

In our beginning tvOS development tutorial, you created your first-ever TVML app. You saw how simple it can be to start developing for the Apple TV, and how easy it is to play video in a fully-featured player. However, the UI design of your app left a bit to be desired.

In this tutorial, you’ll learn how to use the plethora of TVML templates that Apple has provided to make some stunning interfaces. You’ll use these templates to build a comprehensive screen for RWDevCon 2015 videos, which will include a wide range of information about the video and display it in an appealing and recognizable manner.

In the process, you’ll learn how to use TVML templates to make great looking user interfaces, and you’ll learn how to use a templating engine to populate pages (rather than hard-coded data). Let’s get started!

Getting Started

The app you’ll build in this tutorial is called wenderTV; it lets you browse and watch raywenderlich.com video content on your Apple TV, so you can enjoy a vast quantity of knowledge – and bad jokes – from the comfort of your sofa.

I’ve prepared a starter project for you that includes the resources you’ll need for this tutorial. Download the starter project, unzip, and build and run from Xcode.

You’ll notice the screen is completely blank. This is because there aren’t any TVML documents in wenderTV yet. Before you can turn your attention to creating some TVML documents, you have a little housekeeping to do.

Note: Although you can use Xcode to write all the code you need for a TVML-based app, it can be quite a painful experience. Xcode is good for editing Swift or Objective-C, but has only rudimentary support for XML and JavaScript. It’s definitely worth using Xcode when you need to, and then switching to a more capable editor such as Sublime Text, Atom, Visual Studio Code or MacVim to work with TVML documents and JavaScript files.

Loading Scripts

In the previous tutorial, you learned how to split your JavaScript code across several files. The project for wenderTV already has a starter file to help you load app resources (ResourceLoader.js), so you need to import this into the Javascript file you load in application(_:didFinishLaunchingWithOptions:) (main.js).

Remember that TVJS provides the evaluateScripts() function which takes an array of URLs of JavaScript files and a callback. The function reads each URL in turn, attempts to parse it and adds any contained functions and object definitions to the context’s global object. Finally, it invokes the supplied callback with a boolean denoting success or failure.

The AppDelegate contains code that provides the JavaScript application with a list of URLs to the required JavaScript files as part of the launch options object. Open main.js and add the following code to the App.onLaunch() function:

// 1:
evaluateScripts(options.initialJSDependencies,
  function(success){
    if (success) {
      // 2:
      resourceLoader =
        new ResourceLoaderJS(NativeResourceLoader.create());
      var initialDoc = loadInitialDocument(resourceLoader);
      navigationDocument.pushDocument(initialDoc);
    } else {
      // 3:
      var alert = _createAlert("Evaluate Scripts Error",
        "Error attempting to evaluate the external JS files.");
      navigationDocument.presentModal(alert);
 
      throw ("Playback Example: unable to evaluate scripts.");
    }
  });

Taking this new function body step-by-step:

  1. The options object is prepared in Swift and then passed to the JavaScript app in the app delegate, while the initialJSDependencies property is an array of URLs for the different JavaScript files that need to be loaded as the app starts. evaluateScript() performs this action and then invokes the callback indicating whether it was successful.
  2. If the JavaScript sources evaluated successfully, create a ResourceLoaderJS object (from the helper file provived), before using it to load the initial document and then pushing this TVML document onto the navigation stack. loadInitialDocument() is currently a stub method you’ll populate in a bit.
  3. If the JavaScript files failed to load, there is nothing more that the app can do. Therefore it uses _createAlert(), defined at the bottom of main.js, to build and present an alert document and then throw an error.

Add the following to loadInitialDocument():

return resourceLoader.getDocument("video.tvml");

This uses the resource loader helper to get the TVML file from the app’s resource bundle and return it as a DOM object.

Next up, you’ll need to create the file your app is trying to load: video.tvml.

Head into Xcode, and right-click on the layouts group in the project navigator. Select New File…:

01_layouts_group

Navigate to tvOS\Other\Empty and hit Next. Name the file video.tvml and ensure that the wenderTV target is checked:

02_video_tvml

If you get a prompt that says video.tvml already exists, choose Replace.

Open the new file in either your favorite XML editor (or Xcode, if you insist) and add the following lines:

<?xml version="1.0" encoding="UTF-8" ?>
<document>
  <productTemplate>
  </productTemplate>
</document>

This defines a simple TVML document using a new type of template: productTemplate.

Build and run the app to see how things look so far:

03_bar_01

Hm. The screen is still blank. Although you’ve created the file and provided the template, you haven’t provided any content. You’ll fix that shortly, but first we should review a bit of background on on TVML templates.

TVML Templates

Remember that a TVML document is just an XML (eXtentible Markup Language) document with a specific schema defined by Apple. If you’ve used HTML in the past, XML will look familiar. HTML isn’t actually XML (despite some failed efforts with XHTML), but the syntax and structure is fairly similar.

Since TVML documents are XML they should start with the following line:

<?xml version="1.0" encoding="UTF-8" ?>

This is known as the XML prologue; it notes this file is an XML document and which character encoding it uses.

The TVML document has a root <document> element, which is a single element at the top level of the document and the parent (or ancestor) of all other elements. The <document> element has a single direct descendant, which can be one of 18 possible template tags.

A template tag specifies the top-level layout tvOS should use to render the document on screen. In addition to specifying the appearance on-screen, template tags also convey some semantic information about the content they contain. Templates might look similar, but have entirely different purposes.

TVML templates can be divided into the following categories:

  • Informational: Shows a small amount of information to the user, and optionally requests input from the user. It’s not designed for browsable content. Includes alertTemplate and loadingTemplate.
  • Data Entry: The user experience of entering data on TVs is pretty horrendous, and Apple TV is no different. However, there are a few templates for requesting data from the user, including searchTemplate and ratingTemplate.
  • Single Item: Displays information or content about a single product or item, such as a film or episode in a TV series. Includes productTemplate, oneupTemplate, compilationTemplate and showcaseTemplate.
  • Collections: Displays a collection of products, such as a TV series, a genre of films or tracks on an album. Includes stackTemplate, listTemplate and productBundle.
  • Other: Includes menuBarTemplate, which hosts a set of other templates, and divTemplate, which is a completely clean slate upon which you draw.

This tutorial will cover a few of the templates, and then you should have enough of an understanding to use any templates you’d like in your apps.

Note: The Apple reference is a great resource for getting the low-down on the different templates, their capabilities and layouts: apple.co/1PJuOAV.

The Product Template

The first document you’ll create uses <productTemplate>, which is designed to display all information relating to a specific product — in this case, a video.

Open video.tvml and add the following code between the <productTemplate> tags:

<banner>
  <infoList>
    <info>
      <header>
        <title>Presenter</title>
      </header>
      <text>Ray Wenderlich</text>
    </info>
    <info>
      <header>
        <title>Tags</title>
      </header>
      <text>development</text>
      <text>teams</text>
      <text>business</text>
    </info>
  </infoList>
</banner>

This code snippet introduces a lot of new element types. Taking them one-by-one:

  • <banner>: Displays content across the top of a page.
  • <infoList>: Displays a list of <info> elements, arranged in a vertical list.
  • <info>: A container for the content to appear as an item in an <infoList> or an <infoTable>.
  • <header>: Serves as a description of the content of the section in which it resides.
  • <title>: Contains the text of a short title.
  • <text>: Displays text.

Build and run the app to see what your new TVML looks like:

04_bar_02

You can almost make out the new entries down the left-hand side, but the current background color makes it difficult to see the white text.

Update the opening tag of <productTemplate> to match the following:

<productTemplate theme="light">

The theme attribute can either be dark or light and can be used on many TVML templates. It controls the visual effect applied to the background and the colors used in the foreground fonts.

Build and run again; you’ll see the content clearly:

05_bar_03

This page represents a video, but it currently lacks a title. Time to change that.

Add the following inside the <banner> tags, just after the </infoList> closing tag:

<stack>
  <title>Teamwork</title>
  <row>
    <text>17m 54s</text>
    <text>Inspiration</text>
    <text>2015</text>
    <badge src="resource://nr" />
    <badge src="resource://cc" />
    <badge src="resource://hd" />
  </row>
</stack>

This section introduces more TVML elements:

  • <stack>: Stacks lay out their content vertically down the screen in a manner similar to <infoList>. There’s a wider range of tags that can be in a Stack.
  • <row>: A row is like a stack, but with a horizontal orientation instead of vertical.
  • <badge>: Displays a small inline image. The URL is provided by the src attribute.

Notice that the URL of the two badge images begin with resource://. This is a special URL scheme that points to images that exist within tvOS itself. These images include common action icons, such as “play”, rating images for different countries and video information such as HD.

Note: For a full list of the resource images available within tvOS, check out Apple’s documentation at apple.co/1T930o9.

Build and run again to see how the page is shaping up:

06_bar_04

It’s starting to look good, but there’s still a long way to go. Before continuing with the template coding, you first need to consider the separation of data and view.

Data Injection

As your video document currently stands, all the data is hard-coded. To show information about a different video, you’d have to create a whole new page. If you wanted to reformat the video page once you’ve created all the pages, you’d have to go back through and edit every single one of them.

A much better approach is to use a templating engine, where you build the video page as a template and specify where the data should be injected. At runtime, the template engine takes the page template along with the data and generates the TVML page for tvOS to display.

Note: The word “template” is now being used for two different purposes: TVML templates are the layouts provided by tvOS that render your documents on the screen, whereas a templating engine uses template documents combined with data to generate complete TVML documents. Don’t worry too much about the distinction; it’ll be much more clear once you’ve used them both.

Mustache.js is a popular simple templating engine for JavaScript. You might recognize the templating syntax which is based around curly-braces:

{{property-name}}

The Mustache.js library is already part of wenderTV, but you need to build the mechanisms to use it. This presents you with several tasks to accomplish:

  • The data is stored as JSON files in the app bundle. The JavaScript app needs the ability to request them.
  • When a document is loaded, it now requires data, and this should be combined with the document string using Mustache.js.
  • Images that are present in the app bundle need their complete URL substituted.
  • The video document should be updated to turn it into a templated document.

You’ll address each of these in order.

Reading JSON from the app bundle

Open ResourceLoader.js and add the following method to ResourceLoaderJS:

getJSON(name) {
  var jsonString = this.nativeResourceLoader
    .loadBundleResource(name);
  var json = JSON.parse(jsonString);
  return json;
}

This function uses the native resource loader to pull the JSON file from the app bundle before parsing it into a JavaScript object.

Note: It would be relatively simple to replace this functionality with a method that calls a remote server for data instead of finding static data inside the app bundle. The rest of this templating approach would continue to work as it stands.

Injecting data into the document string

Now that you can obtain the data for a given page, you need to combine it with the document template itself. Update getDocument() in ResourceLoaderJS to match the following:

getDocument(name, data) {
  data = data || {};
  var docString = this.nativeResourceLoader
    .loadBundleResource(name);
  var rendered = Mustache.render(docString, data);
  return this.domParser.parseFromString(rendered,
    "application/xml");
}

Here you’ve added an additional data argument to the method, and used the render method on Mustache to convert the template and data to a completed document. As before, you use a DOMParser to convert the document string to a DOM object.

Resolving image URLs

For simplicity’s sake, your sample data stores images as the names of files in the app bundle, which need to be converted to URLs before you can display them. The utility functions to do this are already in the resource loader, so you just need to call them. You’ll do this at the same time as you update the initial document loading to use the templating engine.

Open main.js and update loadInitialDocument() to match the following:

function loadInitialDocument(resourceLoader) {
  var data = resourceLoader.getJSON("teamwork.json");
  data["images"] = resourceLoader
    .convertNamesToURLs(data["images"]);
  data = resourceLoader
    .recursivelyConvertFieldsToURLs(data, "image");
  data["sharedImages"] = _sharedImageResources(resourceLoader);
  return resourceLoader.getDocument("video.tvml", data);
}

First, you load the data using the new getJSON() method. Then you use the utility functions to perform the image name-to-URL conversion. These convert three different image name sources:

  • Each value in the images object on the data array.
  • Every value associated with a key of image anywhere within the JSON data structure.
  • A set of shared images that are useful for all documents in wenderTV.

That takes care of the plumbing underneath; you’re ready to use this powerful new functionality.

Using the Mustache.js templates

Open teamwork.json and take a look at the data you’ll use to populate the video page. There’s quite a lot of data, but it’s a standard JSON object and fairly easy to understand. You should spot some fields such as title, presenter and duration that you’ve already hard-coded into video.tvml. You’re now going to swap these out.

Open video.tvml and find the title tag that contains Ray Wenderlich. Replace the name with {{presenter}}, so that the first <info> section now looks like this:

<info>
  <header>
    <title>Presenter</title>
  </header>
  <text>{{presenter}}</text>
</info>

The syntax for Mustache.js is really simple; it will replace {{presenter}} with the value of presenter in the data object supplied to it.

Now that you’ve got the hang of that, you can replace the following content with the respective template tags:

  • Teamwork{{title}}
  • 17m 54s{{duration}}
  • Inspiration{{category}}
  • 2015{{year}}
  • "resource://nr""resource://{{rating}}

Build and run; you shouldn’t see any difference, which is exactly what you want. The page is now data-driven, and even better, you didn’t break anything. Bonus! :]

There are still some parts of the template you haven’t touched: closed-captions, HD and tags. These use some slightly more advanced parts of the Mustache.js templating engine.

Template sections

Remove the three <text> tags in the Tags section and add the following in their place:

{{#tags}}
  <text>{{.}}</text>
{{/tags}}

This new syntax defines a template section. Look at teamwork.json and you’ll see that tags is an array of strings. The Mustache.js syntax here loops through the array, with {{.}} rendering the content of the current index.

Finally, you need to handle the two boolean badges. Replace the cc and hd badges with the following:

{{#closed-captions}}
  <badge src="resource://cc" />
{{/closed-captions}}
{{#hd}}
  <badge src="resource://hd" />
{{/hd}}

Once again you’re using sections, but this time they’re structured like an if-statement. If the specified property exists and has a true value, then render this section; otherwise, ignore it.

Build and run again; check out your newly templated video page.

To confirm that the data injection is actually working, open main.js and change the data file loaded in loadInitialDocument() from teamwork.json to identity.json. Build and run again to see the data change:

07_bar_05

You can now see details of Vicki’s talk on identity — sweet!

Filling out the TVML Template

The video page is still looking a little barren. It’s time to double-down on adding some content.

Open video.tvml and add the following inside the <stack>, just below the existing <row>:

<description allowsZooming="true"
  moreLabel="more">{{description}}</description>
<text>{{language}}</text>
<row>
  <buttonLockup type="play">
    <badge src="resource://button-play" />
    <title>Play</title>
  </buttonLockup>
  <buttonLockup type="buy">
    <text>$9.99</text>
    <title>Buy</title>
  </buttonLockup>
</row>

Once again, this introduces some new TVML elements:

  • <description>: Displays a larger amount of text that’s used to describe content. If the text is too long for the display area then a label will be displayed with a title defined by the moreLabel attribute.
  • <buttonLockup>: A lockup is a class of element that joins its children together as a single element. A button lockup can contain text and a badge and will appear as a button.

Remember that these elements are all contained within a <stack>, so they’ll appear on top of each other.

Before checking your work, you need to add one more element to the top banner. Add the following line immediately after the </stack> closing tag:

<heroImg src="{{images.hero}}" />

A heroImg element is a large image that defines the content of this document. It appears inside the <banner> and tvOS uses it to define the blurred page background.

Build and run to see the completed top banner:

08_bar_06

It’s starting to look really cool! Now that you’ve provided a hero image for tvOS to use when it generates the background, you can investigate the effects of the theme attribute.

Find the opening <productTemplate> tag and change the theme attribute from light to dark. Build and run again to see the difference:

09_bar_07

The visual effect on the background has changed along with the foreground font colors.

Adding Shelves

The remainder of the productTemplate is made up of “shelves”. A shelf is a horizontal section of the page with content elements scrolling on and off the screen.

Add the following below the closing </banner> tag, towards the bottom of video.tvml:

<shelf>
  <header>
    <title>You might also like</title>
  </header>
  <section>
    {{#recommendations}}
      <lockup>
        <img src="{{image}}" width="402" height="226" />
        <title>{{title}}</title>
      </lockup>
    {{/recommendations}}
  </section>
</shelf>

This shelf displays a set of other videos that the user might enjoy as defined in the recommendations property of the data model. Each recommendation has an image and a title, each of which you use in the code above.

There are two other elements introduced in this code segment:

  • <section>: Defines a set of related content that should all be laid out together. A section can contain a title and multiple lockup elements.
  • <lockup>: You saw <buttonLockup> before; lockup is a more general type of lockup. It provides layout for an image, a title, a badge and a description.

Now add another shelf below the one you just created:

<shelf>
  <header>
    <title>Production</title>
  </header>
  <section>
    {{#people}}
      <monogramLockup>
        <monogram firstName="{{firstname}}"
          lastName="{{lastname}}"/>
        <title>{{firstname}} {{lastname}}</title>
        <subtitle>{{role}}</subtitle>
      </monogramLockup>
    {{/people}}
   </section>
</shelf>

This shelf displays a list of people associated with the production; it’s stored in the people property in the data model.

This introduces the <monogramLockup> and <monogram> elements, which let you represent a person when an avatar isn’t available. Like the other lockup elements, a monogram lockup simply locks its content together.

A monogram has firstName and lastName attributes, from which it generates a monogram (initials) and places it in a large circle.

Build and run to see how your shelves are taking shape (use the Apple TV remote to scroll):

10_bar_08

Take a look at the description for the Identity talk. Notice that the more label has appeared, because there is too much text to display in the available space. Navigate to the label and you’ll see you can focus on the description and press select to trigger an action. This action doesn’t currently do anything, but wouldn’t it be nice if it would display the full text?

Time for another TVML template.

Handling Text Overflow

The descriptive alert template provides space for an extended area of text and buttons. It sounds ideal for this purpose. You’ll use this template and a spot of JavaScript to wire it up.

In the Xcode project right-click on the layouts group and select New File…. Choose tvOS\Other\Empty and name the file expandedDetailText.tvml. Choose Replace if prompted. Open the new file and add the following:

<?xml version="1.0" encoding="UTF-8" ?>
<document>
  <descriptiveAlertTemplate>
    <title>{{title}}</title>
    <description>{{text}}</description>
    <button action="dismiss">
      <text>Dismiss</text>
    </button>
  </descriptiveAlertTemplate>
</document>

This should be quite straighforward to understand. There’s the usual XML prologue, the <descriptiveAlertTemplate> tag and some elements you’ve used before. Notice that the button tag has an action attribute; this is a user-defined attribute that’s not part of the TVML specification.

You can define any attributes that you want (provided they don’t clash with existing attributes) and then read them from your JavaScript app. You’ll write some code to handle this dismiss action in just a bit.

Open video.tvml and find the <description> tag. Update the element to match the following:

<description allowsZooming="true"
  moreLabel="more"
  action="showOverflow"
  title="{{title}}">{{description}}</description>

You’ve added two new attributes: action and title. You’ll use both of these in the event handler to create the expanded detail text document.

Event handling

Now that the document templates are ready to go you can turn your attention to the JavaScript that wires everything up.

Open main.js and add the following function:

function _handleEvent(event) {
  // 1:
  var sender = event.target;
  var action = sender.getAttribute("action");
  // 2:
  switch(action) {
    case "showOverflow":
      // 3:
      var data = {
        text: sender.textContent,
        title: sender.getAttribute("title")
      };
      // 4:
      var expandedText = resourceLoader
        .getDocument("expandedDetailText.tvml", data);
      expandedText.addEventListener("select", _handleEvent);
      navigationDocument.presentModal(expandedText);
      break;
    case "dismiss":
      // 5:
      navigationDocument.dismissModal();
      break;
  }
}

Taking this piece-by-piece:

  1. The target property of the event argument represents the DOM object that fired the event. The getAttribute() method of a DOM object will return the value for the specified attribute. Here you’re using it to find the value of the action attribute you added above.
  2. Switch on the action attribute to invoke the appropriate code.
  3. If the action is showOverflow, then you have a description field with too much content. Construct an object with the data required by the expanded detail text document. Once again you’re using getAttribute() along with textContent, which returns the content of the tag itself.
  4. Load the expandedDetailText.tvml document in the usual way, add an event listener and use the presentModal() method on NavigationDocument to display the new document on top of the current document.
  5. If the action is set to dismiss then use the dismissModal() method on NavigationDocument to perform the dismissal.

Now that you’ve created this event handler, you need to wire it up to the initial document. Add the following line to App.onLaunch, just after you call loadInitialDocument():

initialDoc.addEventListener("select", _handleEvent);

As you saw in the previous tutorial, this registers the _handleEvent function as a listener for the select event, and uses event-bubbling to handle all events triggered within the document.

Build and run the app, navigate down to the over-full description and hit the select button. You’ll see your new expanded detail text page:

11_bar_09

You can use the dismiss button to return to the video screen.

Now that is one swell-looking – and extensible – interface.

Where To Go From Here?

Here is the example code from this TVML tutorial.

TVTThumb

In this tutorial, you accomplished a lot – you created a great-looking TVML app using TVML templates, and integrated a Javascript templating engine to separate the UI from the data.

If you’d like to learn more, you should check out our book the tvOS Apprentice. The chapter in the book goes into further detail and show you how to make the TVML to allow users to rate videos from 1-5 stars. You also might enjoy the other 27 chapters and 500+ pages in the book! :]

In the meantime, if you have any questions or comments about using TVML templates, please join the forum discussion below!

The post tvOS Tutorial: Using TVML Templates appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4370

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>