How to mix UIKit and TVMLKit within one app?

Posted by Wei on March 16, 2017

Question

I’m exploring tvOS and I found that Apple offers nice set of templates written using TVML. I’d like to know if a tvOS app that utilises TVML templates can also use UIKit.

Can I mix UIKit and TVMLKit within one app?

Answer One

Yes, you can. Displaying TVML templates requires you to use an object that controls the JavaScript Context: TVApplicationController.

var appController: TVApplicationController?

This object has a UINavigationController property associated with it. So whenever you see fit, you can call:

let myViewController = UIViewController()
self.appController?.navigationController.pushViewController(myViewController, animated: true)

This allows you to push a Custom UIKit viewcontroller onto the navigation stack. If you want to go back to TVML Templates, just pop the viewController off of the navigation stack.

If what you would like to know is how to communicate between JavaScript and Swift, here is a method that creates a javascript function called pushMyView()

func createPushMyView(){

    //allows us to access the javascript context
    appController?.evaluateInJavaScriptContext({(evaluation: JSContext) -> Void in

        //this is the block that will be called when javascript calls pushMyView()
        let pushMyViewBlock : @convention(block) () -> Void = {
            () -> Void in

            //pushes a UIKit view controller onto the navigation stack
            let myViewController = UIViewController()
            self.appController?.navigationController.pushViewController(myViewController, animated: true)
        }

        //this creates a function in the javascript context called "pushMyView". 
        //calling pushMyView() in javascript will call the block we created above.
        evaluation.setObject(unsafeBitCast(pushMyViewBlock, AnyObject.self), forKeyedSubscript: "pushMyView")
        }, completion: {(Bool) -> Void in
        //done running the script
    })
}

Once you call createPushMyView() in Swift, you are free to call pushMyView() in your javascript code and it will push a view controller onto the stack.

Answer Two

If you already have a native UIKit app for tvOS, but would like to extend it by using TVMLKit for some part of it, You can.

Use the TVMLKit as a sub app in your native tvOS app. The following app shows how to do this, by retaining the TVApplicationController and present the navigationController from the TVApplicationController. The TVApplicationControllerContext is used to transfer data to the JavaScript app, as the url is transferred here :

class ViewController: UIViewController, TVApplicationControllerDelegate {
    // Retain the applicationController
    var appController:TVApplicationController?
    static let tvBaseURL = "http://localhost:9001/"
    static let tvBootURL = "\(ViewController.tvBaseURL)/application.js"

    @IBAction func buttonPressed(_ sender: UIButton) {
        print("button")

        // Use TVMLKit to handle interface

        // Get the JS context and send it the url to use in the JS app
        let hostedContContext = TVApplicationControllerContext()
        if let url = URL(string:  ViewController.tvBootURL) {
            hostedContContext.javaScriptApplicationURL = url
        }

        // Save an instance to a new Sub application, the controller already knows what window we are running so pass nil
        appController = TVApplicationController(context: hostedContContext, window: nil, delegate: self)

        // Get the navigationController of the Sub App and present it
        let navc = appController!.navigationController
        present(navc, animated: true, completion: nil)
    }

Reference


This is the end of post