Search code examples
uikittvosapple-tvtvml

Can I mix UIKit and TVMLKit within one app?


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?

I found a thread on Apple Developer Forum but it does not fully answer this question and I am going through documentation to find an answer.


Solution

  • 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.

    SWIFT 4.1 UPDATE

    Just a few simple changes to method names and casting:

    appController?.evaluate(inJavaScriptContext: {(evaluation: JSContext) -> Void in
    

    and

    evaluation.setObject(unsafeBitCast(pushMyViewBlock, to: AnyObject.self), forKeyedSubscript: "pushMyView" as NSString)