App Frameworks • tvOS • 41:45
Learn about advanced techniques in TVML based apps, such as bridging between native and javascript code, creating your own custom views for use inside of templates, creating your own custom templates, how to introduce UIKit view controllers alongside TVMLKit based templates, and additional best practices you can apply to your apps.
Speaker: Jeremy Foo
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
All right.
[ Applause ]
Good afternoon everyone and welcome to app development using TVMLKit Part 2. There are many amazing apps out there on the App Store based on TVMLKit. And some have even been customized with styles to give a unique experience and branding for all your apps. So today I'm extremely excited to be talking to you, to tell how you could bring your apps to a whole other level. And this is done by extending TVMLKit by extending interfaces and also the functionality that's provided in JavaScript. This will basically let your app stand out even more with all your own unique branding.
TVMLKit is a thoughtful framework, we've also tried to make sure that there is some thought in how it's structured as such that only two extension points that you have to worry about. In the first half of the talk I'm going to tell you guys how you can extend the user interface in TVMLKit by extending templates. In the second half, my colleague Christopher will be explaining how you can enhance your apps by adding application functionality to the JavaScript engine that powers TVMLKit. So, let's talk about extending templates.
The very first thing that everyone encounters when they use TVMLKit is this idea of templates, these are defined in the TVML, TV mark-up language. They use XML as a way to describe what's on the screen and when people encounter this, they start by saying huh, I know this this is a web browser. I can assure you we are absolutely not a web browser. What TVMLKit is however, is a powerful templating engine, but that doesn't mean you are limited to what we provide.
In fact, Apple's own apps use TVMLKit but extend it in really subtle ways that allow it to coexist with the rest of the framework and still feel right on tvOS. Let's look at, take a look at an example, this is the App Store app. The App Store theme had a requirement for a unique cell within a shelf, as you can see they have reused what TVMLKit provides, an image and three labels. But over here they have a custom layout with custom focused behaviors.
Another example that we are all really familiar with is the concept of the Buy button. Once again the App Store theme had a requirement that this button had to reflect the various installation states of an app. Whether you're buying the app, whether the app is being installed or downloaded, they did all these by reusing what TVMLKit provides. And that's the common theme we want to stress to you today. Reuse TVMLKit as much as possible and extend for the parts that we do not provide.
So templates. If you've used TVMLKit it follows a set path when it, when you inject it into the framework it ends up on the screen. TV mark-up language is consists of multiple elements, all with unique names. And when it enters the framework it goes to the TVElementFactory. This is the central registry that knows about all elements and is able to translate that into data structures that the rest of the framework understands. The TVInterfaceFactory is the class we use to generate user interfaces and place them currently on the screen. And this is how TVML ends up on your screen. So let's look at what happens when we extend templates.
When it comes to extension of templates, it's as simple as adding your own mark-up in the TVML that you send to us. It goes through the same process, it enters the element factory, gets translated, but when it hits the TVInterfaceFactory, because we do not know about your user interfaces, we'll ask you for it.
When you've given us the user interface the TVInterfaceFactory will put it on the screen itself, very simple. So now that you have an idea of how everything works, let's look at things in detail. There are three steps that you have to worry about when you want to extend templates, and the very first thing is to define mark-up.
Mark-up in TVML is basically XML. And you have an idea of what your user interface looks like on the screen. You have to translate that into a structure that kind of resembles what it should appear on the screen in mark-up itself. In our example, we wanted a banner on the stackTemplate.
This banner had an animated background and if you wanted to be able to control the animations using a switch, within TVML. Therefore, this element, myBanner has a property called animated. Additionally, we also wanted a button, but because there is no requirement for additional functionality, we could just use what TVML provides, and this is a form of a button.
So now that you have an idea of what your user interface is represented in mark-up you have to tell the framework about it. And this is as simple as registering your unique element name with us. Registration has to be done once before application controller startup so that we're fully aware of all the elements that we have to take care of.
It's as simple as telling the TVElementFactory that you want to associate a TVViewElement class and your custom, element name. The TVViewElement is the base data structure that we use to translate your mark-up into something that the rest of the framework understands. Along with TVViewElement, there are two other subclasses that we use. TVImageElement deals with images specifically and TVTextElement has special handling for text itself. Now that we have registered the elements, this is the next step, the third step that involves a little work on your end, but still we try to keep things simple, with only just two steps.
The very first thing you have to do is to setup an interface creator that conforms to the TVInterfaceCreating protocol. This particular object is the one that vends your user interface when we ask for it and there are some call back methods that you have to implement from the TVInterfaceCreating protocol declaration that pertains to what sort of UI you want to return.
The next step is when we call into your interface creator class, configure your user interface and return it to us. As I stressed before, reuse TVMLKit as much as possible because we have done a lot of work to ensure that our elements are performing and look and feel good onto tvOS. So let's take a look at an example of what this interface creator looks like.
Over here I have a MyInterfaceCreator class that conforms to TVInterfaceCreating. Because we are only interested vetting a view for the better we are going to implement the makeView element, existingView call back method. Once this class has been set up, all you have to do is to register an instance of it with the TVInterfaceFactory.
Looking at makeView in detail, we can see that TVMLKit furnishes you with an element and a optional existingView. So in the case of the banner, we just need to inspect the element name itself. We're particularly interested in the myBanner element name and when we find something like that, we create an instance of this view.
Because the myBanner has an element property we can easily inspect this using the element that the call back method provides and then assign it to the view itself. We also know that we have a button in the view, and because this is a TVMLKit button, it's as simple as asking the TVInterfaceFactory for this particular element and returning the view to all myBanner view. Finally of course, return your view. In the case of default framework behavior, you have to return nil.
Now we've talked a lot about views, what about view controllers? TVMLKit has view controllers and they are exposed in a form of a shelf lockups that expands horizontally or a grid that expands vertically. So by using the TVMLKit call back to return view controllers you can substitute these for your own view controllers.
The usage is similar to makeView and in this case, we still furnish you with an element but an existingViewController and we expect an existingViewController to be returned. Now you're thinking, Aha! Collection of views. Collection of views has views that are cells, so this is how I can customize my own lockups.
Remember this example? The App Store theme specifically only had a requirement to have a unique cell. Of unique custom layouts and focus behaviors, everything else, as you can see, is extended to UMLKit shelf. The titles go up and down depending on whether the focus is on the element beneath it.
So, new in tvOS 10, we are allowing you to specify your own custom collection view cell. This allows you to have custom layouts for the cell and most importantly participate in focus events when the cell comes into focus. To do this there are two things you need to do. The very first thing is to use this new API that we're exposing that allows you to give us a collectionView CellClass when we ask for it for a particular element.
And when we build the user interface on the screen, we would use makeView to allow you to customize, configure the cell and return it to us. We would provide you the custom collection view cell that's deep queued from the collection view using the existing view parameter itself. So I'd like to invite Parry on stage to give a demonstration of how this works, Parry.
[ Applause ]
Thank you, Jeremy. Hi, my name is Parry and I'm going to demo you how to use custom cells in TVMLKit. Now custom cells are really nice, if you want to use TVMLKit collections like shelves and grids, but you want to add your own cell to showcase your content through a custom layout and focus mechanism. So I'm going to show you a sample app that I am working on based on TVMLKit. And then later on, I'm going to enhance it with custom cells to make appear better. So let's get to it.
So I'm going to switch over to my computer, and I have here Xcode project open for my application that I'm working on based on TVMLKit. Now let me talk about this application. It's an app that lets users browse through their photos. So the application that JavaScript and the associated XML files are actually hosted on my machine, and let me go over them right in front of you.
So here's one of the pages from that app that is going to showcase user's photo albums. Now you can see it's a stacked template that has a nice banner with a background image, and it has a shelf to showcase the albums and each album is basically represented by a lockup.
Now each one of these lockups embeds an image from the album that it represents. Other than some custom styling to make this lockup a little bigger, I have not customized TVMLKit at all. So it's kind of out of stock TVMLKit app. So let's run this real quick and see what it looks like right now. And for that I'm going to switch over to Apple TV.
So there's the app, that's the stacked template document that I talked about. So you see the nice banner on the top and the shelf at the bottom. And you know, I have to say just out of the box it looks pretty nice. It's functional and the bigger lockups do make a lot of difference. But I think it can be a little better.
So, consider this, how about instead of just taking one photo from the album, to represent it on this page. What if we chose multiple photos, featured photos from that album, and use that to put the album on this document? Now I'm going to just think about it a little.
How about we create a collage of these photos when the album is not focused, and then fly them out into a nice grid when the album does get focused? That will be cool. But more than that, it'll give a nice context to the user of what the album is about. Because the user gets to see more photos from the album.
Now this is a really nice use case for having custom cells with TVMLKit. So let me show you how to do it. So I'm going to switch back, to my computer and the first thing I want to do is create a mark-up spec for what I just said. It's kind of a translation of what your visual representation of your user interface is in terms of an XML that TVMLKit can understand.
So I have that here right now, and as you can see, it's again a stacked template that has a shelf, but instead of lockups, it has a new element that I have created called FlyoutCell. That's the cell that I just described. I'm calling it flyout because the images just fly out from it. And as mentioned in the description, I want multiple photos in this cell, hence, I have multiple image elements as children.
Now there's one important thing to note, is that for all custom cells, they have to be accompanied with three styles, and they are width, height, and a new style we've added into tvOS 10 called TV focus margin. Now TVMLKit uses width and height to give your cells a nice frame, in the collection. And it uses TV focus margin as a hint to know how much your cell is going to grow when it gets focused. It uses this information to create nice spacing between shelves, and also to readjust the shelf header when the cell underneath it gets focused.
So now that we have the spec, what's the next step? We know we have to add this new element into TVMLKit, so let's do that real quick. So for that I'm going to head over to AppDelegate and in the method application didFinishLaunching WithOptions. I'm going to quickly drop the code that I wrote earlier, to do just that. So using this I've just added my FlyoutCell, using TVElementFactory to TVMLKit. Now TVMLKit also wants me to enhance the interface creator by extending it, so that I can configure my cell myself, right. So let's do that real quick as well.
So, that's all the configuration required for TVMLKit, now let's have a look at the implementation of these classes, starting off with the ExtendedInterfaceCreator. Now for custom cells you need to implement two APIs. First, the collectionViewCellClass, this is required to map your custom elements to the collectionViewCellClass you want to use for it. And second, makeView, this one is required to configure your cell with your element. So let's quickly fill in these blanks, with a bit of code that I've written before. So there goes the mapping for my custom cell with the collectionViewCellClass that I want to use for it.
And similarly, the mapping to configure that collectionViewCell with my element. Now an important thing to note here is that the existing view parameter would always be valid, because TVMLKit just dequeued it from the collection view to get it configured. So it's always going to be there, for custom cells. Now before I jump into the configuration of my flyout cell, let's have a look at the FlyoutCell class itself.
So that's the FlyoutCollectionViewCell class that I'm using to represent my flyoutCell. And as you can see, it basically subclasses a UICollectionViewCell, in itself, it's got nothing to do with TVMLKit, it's just a cell class. I can use it in a native app or any other app. The important point to note here is that it accepts an array of image views that it uses to configure its contents. And these image views are the ones that we want to configure in our extended interface creator from the TV view element. So let's head back to extended interface creator and add that code.
So that's the configuration, it's not much, but the important part here is this. I am trading over all the children of the element and extracting the image elements out of it and simply reusing TVMLKit to create an image view for me. Now out of the box, this is really, really good because what it gives me is downloading of remote images, it gives me scaling and cropping to fit the bounds so that the user interface is performing. And it also gives me some caching in case I reuse those images somewhere else. So reusing these TVMLKit views, and other infrastructure is really beneficial. I highly encourage it.
So, with that we've configured all the code and everything in TVMLKit knows about our element. There's only one thing remaining to do, which is to go back to the stacked document that I showed earlier, and replace all the lockups with the FlyoutCell that I have just added. So I'm going to head back there, remove all of these lockups, and add my FlyoutCell.
Now there were three albums, there are three FlyoutCells still, each FlyoutCell has, four images in it. And when you add these cells, don't forget to add the style as well, that's required, it's a must. So now that we've added everything, let's rerun this app and see what it looks like now. So I'm going to switch over to Apple TV, and there's the flyout cell.
[ Applause ]
And you can immediately tell it looks so much better, then just having one image, it just looks so much more immersive. And notice that the cell just fits into TVMLKit, all the spacing is right, even the header, the album, it moves up and down as the cell underneath it gets focused.
It just feels like it belongs inside TVMLKit, although you added it. So there you have it, custom cells are really easy to implement, and it lets you add a lot of value to your application, by reusing what TVMLKit provides but by also showcasing your content in the way you want. Thank you.
[ Applause ]
Thank you, Parry. I think Parry spend more time building the FlyoutCell than actually configuring TVMLKit for that. So before we continue, I'd like to do a quick recap of what we've seen today. The very first thing when it comes to extending templates is to define a custom mark-up that describes your user interface. Register it with a TVElementFactory so we know about it and are able to translate it into TV view elements. Provide an extended interface creator so that you can vend this user interface whenever we stop building the UI on the screen.
And most importantly, use the TVViewElement and its properties and attributes to configure your user interface before displaying it on the screen. So that's all there is to extending templates, it's really simple. And we are all really excited to see what you can build with this. But before we continue, there are a few important things that we want you to know.
Your user interface is represented by elements within the document in TVML. And this document can be updated at any time. Take for example a shelf with lockups as results, in a search template. As you search this shelf will get updated using JavaScript and the contents will change, that's what a document update is.
Now, when we build the user, rebuild the user interface when document updates happen, we would call into the, extended interface creator, and it is your job to look at the update type in the element and figure out what's changed. For example, the children might change, and as a good parent, you don't really want to throw away children, because that's not a nice thing to do [laughter]. Instead, reuse cells, reuse views as much as possible.
In our initial example for returning the banner, we weren't really being a good citizen in the TVMLKit ecosystem, we didn't reuse views at all. Let's see if we can change this. It's as simple as changing two lines of code, in this case we look at the existingView that's part of the call back and we try to figure out if this view is a view that we expect, in this case a MyBanner view. If it's not, we instantiate a new copy of it.
Now because we have a TVMLKit button, based on TVMLKit, we have to do the right thing as well, and reuse it by passing it to the extended, by passing it to the TVInterfaceFactory, in, the existing view parameter. Now in this particular example, because MyBanner is a really simple view, that is totally fine to update it all the time, we don't really look at the update type. But if your view hierarchy is way more complex. For performance reasons, we highly encourage you to look at the update type and figure out what has changed.
That's the first line that you have to change and the second. Now, new in tvOS 10 is this concept of light and dark appearance. If you have a custom view that you're using with TVMLKit, you can listen to trait collection changes to figure out if you're in light or dark. We have a session, What's New in tvOS that absolutely explains what you must do, and I highly encourage you to check that out.
In the case that you are a good citizen of TVMLKit and you reuse our components, thank you very much. We highly encourage you to check the style update hint from the element update type. This gives you an idea of whether your view is in the light or dark appearance, and this reuses the document update facility and it's the only avenue available for you to update your views for light or dark appearance. Because they are TVMLKit components you have to reuse them. And more importantly, you have to forward into the TVInterfaceFactory so we can do the right thing.
If you have a native app or rather you have your own view controllers and you have a TVMLKit based application, you can mix them with the framework itself. This is as simple as defining a custom template element, once again registering it with element factory. And where we built the user interface for a particular template via loading, return your view controller and you're all set to go.
Another way to use TVMLKit with your existing apps, native apps, is by hosting the navigation controller that we provide, as a sub application. You've already learned how to create a TV application controller, and I'd like to highlight that in this case you do not have to specify a window, because you already have one. Choose the view controller that you're presenting in navigation controller with. Once you have a TV application controller, take its navigation controller and present it.
An alternative to this is to use an instance of UIWindow, and in this particular example, you would have to specify the window parameter. Once JavaScript starts up and application logic executes, TVMLKit will do the right thing and bring your window key invisible. And with that, I'd like to hand over the time to Christopher, to talk to you about how you can extend application functionality through extending JavaScript, Chris.
[ Applause ]
Thanks Jeremy. Hi, I'm Christopher, I'm a TVMLKit engineer. So we just saw how you can extend the templating engine in TVMLKit to use custom mark-up, implement your own views, view controllers, and collection view cells. You can also extend the scripting engine of TVMLKit which is powered by JavaScriptCore to implement your own app specific functionality in JavaScript or expose to JavaScript. There are three main ways to inject code into the JavaScript context of your TVML application, let's take a look. First, you can simply load additional JavaScript libraries at run time.
Second, you can use native code to interact directly with the JavaScript context in your application and do things like invoke functions into JavaScript or pass data back and forth between environments. Third, you can take native classes and you can actually bridge them into JavaScript to make them accessible to your scripts using some simple class conventions in JavaScriptCore. Let's go into detail into each one of these methods starting with the simplest.
When your TVML application loads, the first thing that's going to happen is, TVMLKit is going to fetch a JavaScript file that contains the app.onlaunch call back where control will be handed over to JavaScript for your TVML application. Startup has to wait until this application.js file is fetched and evaluated before app.onlaunch can execute.
If you have a large application, it's a good idea to break up the code into separate files to speed up startup time. This is a common pattern and TVMLKit supports this by allowing you to load additional JavaScript at any point in the application lifecycle. Your JavaScript libraries are where you will define variables and functions that get shared globally across your scripts.
Let's take a look at the evaluate scripts global function that TVMLKit JS provides. Generally you'd want to call this in your app.onlaunch but you can actually use it anywhere. The function takes an array of script URLs and a completion call back as arguments. When your completion call back is executed, you'll be able to use the variables and functions that were defined in your libraries. But there are a few things to keep in mind.
When you call evaluate scripts, TVMLKit JS is going to execute the code, including any side effects in your JavaScript files. So don't call evaluate scripts multiple times on the same file. Also, be careful if you have scripts that depend on other scripts, that you load them in the correct order.
Evaluate scripts fetches the array of script URLs in parallel, and then executes them in sequence. If any of the scripts are unavailable, none of the scripts will execute, so always check the success argument to your completion call back to make sure that your library functionality is available. And finally, I'd just like to remind everyone that even though TVMLKit offers many web like APIs, your TVML application is not a web browser. So you may find some JavaScript libraries on the web that will be helpful to you but some may not be compatible, especially if they rely on browser functionality like a global window object or a global document object.
Just keep that in mind. But you don't have to limit yourself to just JavaScript in your TVML applications, you can actually call into the JavaScript environment from native code. From here you can do things like pipe UI application events down into JavaScript or push and pull data between JavaScript and native.
In TVML, your JavaScript context is managed for you by TVMLKit in a class called the TVApplicationController. JavaScript executes on a separate thread so you have to schedule your interactions with it. In the simplest form, all you have to do is call a method on this TVApplicationController, and pass in a block to be evaluated.
When your block executes, you'll get a reference back to the JS context, from here, you can eval strings as JavaScript code, you can invoke methods on objects, and you can get and set properties into JavaScript using native values. Just make sure that you don't retain this JSContext or use it anywhere outside of the block.
Since your JavaScript is all happening on a separate thread, you also want to be careful that you don't perform any blocking operations while you're evaluating this block, as you may deadlock with the main thread. For more information on JSContext and JavaScriptCore, please see the 2013 WWDC session on JavaScriptCore. Let's take a look at an example of how we would call into JavaScript code from our Swift application. Here we're looking at the Swift code for our TVML app's app delegate. We've implemented a stub here for the UIApplication delegate method to handle custom URL schemes.
If we wanted to pass this URL down into JavaScript, all we have to do is ask the appController to evaluate a block in the JavaScript context. When this block executes, we'll have a reference to the JSContext, from here we can access properties directly on the JavaScript global scope object.
We can take native values or objects and use them as JavaScript properties or in this case, as a function argument. Here, we're invoking the on open URL global function that was defined in JavaScript and passing along a string of the URL from the UIApplication delegate call back. Just like that we've exposed new functionality into our TVMLKit application and exposed application events into JavaScript.
Next, let's talk about bridging. So we just saw how super easy it is to take native values and use them in JavaScript, for simple types like strings, numbers, and arrays this is already handled for you by JavaScriptCore. If you want to use your own classes, all you have to do is follow a few simple class conventions and JavaScriptCore can actually bridge your classes as well. Let's take a look. So there are three main steps here. First, you have to declare a custom protocol that extends JSExport, extends JSExport, which comes from JavaScriptCore.
Second, we're going to define our classes using Swift and extending this native, this protocol to expose our native classes. And third, we're going to take the instance of our classes or the class itself that we want to expose and use a call back on the TV application controller delegate to prepare the JSContext before control gets handed over to JavaScript during app on launch.
Let's take a look at an approach of how we would go about creating a wrapper around store kit if we wanted to expose something like in app purchase functionality to JavaScript. We start off by defining our protocol, here's where we'd specify properties or methods that we wanted JavaScriptCore to bridge for us.
Here we'll just leave it as a stub but you can see that we've extended JSExport and defined a protocol called StoreKitWrapperProtocol. Next, we will create a class for our store kit wrapper that extends NSObject and implements this custom protocol. Once again I've left the details stubbed out but this would be where you'd define your native functionality that you wanted JavaScript to call into.
And finally, we implement the TVApplicationControllerDelegate method to evaluate app JavaScript in context. This gets called before app.onlaunch executes and allows us to expose our custom functionality before control gets handed over to JavaScript. That's really all there is to it. We've now defined a native class, we've gone through the steps to make it accessible to our TVML application's JavaScript.
So in this session we've seen how you can leverage native functionality to enhance your TVML applications. You can extend templates using custom mark-up for your own views and collection view cells. You can actually host TVMLKit inside of a native application or use your own native view controllers inside a TVMLKit.
You can even extend the scripting environment in TVMLKit to bridge native classes, type application events through, and load additional libraries. I hope we've shown you how easy it is to build app specific functionality in your TVMLKit apps that's custom to your brand. TVMLKit is a really simple API for building complex apps with high quality results and minimal overhead.
I encourage you to check out the TVML guide and TVML catalog sample applications for more ideas on how to extend and use TVMLKit. Please also check out the Apple developer website for programming guides, sample code, and documentation. Also, check out these other sessions that happen this year at WWDC 2016, especially Developing Apps for TVMLKit Part 1, which has a great demo of building an app from start to finish. Thank you, and enjoy the rest of WWDC 2016.