How to bridge TVML/JavaScriptCore to UIKit/Objective-C (Swift)?

允我心安 提交于 2019-11-28 09:44:32

This WWDC Video explains how to communicate between JavaScript and Obj-C

Here is how I communicate from Swift to JavaScript:

//when pushAlertInJS() is called, pushAlert(title, description) will be called in JavaScript.
func pushAlertInJS(){

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

        //get a handle on the "pushAlert" method that you've implemented in JavaScript
        let pushAlert = evaluation.objectForKeyedSubscript("pushAlert")

        //Call your JavaScript method with an array of arguments
        pushAlert.callWithArguments(["Login Failed", "Incorrect Username or Password"])

        }, completion: {(Bool) -> Void in
        //evaluation block finished running
    })
}

Here is how I communicate from JavaScript to Swift (it requires some setup in Swift):

//call this method once after setting up your appController.
func createSwiftPrint(){

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

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

        //prints the string passed in from javascript
        print(str)
    }

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

[UPDATE] For those of you who would like to know what "pushAlert" would look like on the javascript side, I'll share an example implemented in application.js

var pushAlert = function(title, description){
   var alert = createAlert(title, description);
   alert.addEventListener("select", Presenter.load.bind(Presenter));
   navigationDocument.pushDocument(alert);
}


// This convenience funnction returns an alert template, which can be used to present errors to the user.

var createAlert = function(title, description) {  

   var alertString = `<?xml version="1.0" encoding="UTF-8" ?>
       <document>
         <alertTemplate>
           <title>${title}</title>
           <description>${description}</description>

         </alertTemplate>
       </document>`

   var parser = new DOMParser();

   var alertDoc = parser.parseFromString(alertString, "application/xml");

   return alertDoc
}

You sparked an idea that worked...almost. Once you have displayed a native view, there is no straightforward method as-of-yet to push an TVML-based view onto the navigation stack. What I have done at this time is:

let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.appController?.navigationController.popViewControllerAnimated(true)
dispatch_async(dispatch_get_main_queue()) {
    tvmlContext!.evaluateScript("showTVMLView()")
}

...then on the JavaScript side:

function showTVMLView() {setTimeout(function(){_showTVMLView();}, 100);}
function _showTVMLView() {//push the next document onto the stack}

This seems to be the cleanest way to move execution off the main thread and onto the JSVirtualMachine thread and avoid the UI lockup. Notice that I had to pop at the very least the current native view controller, as it was getting sent a deadly selector otherwise.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!