问题描述
到目前为止,tvOS
支持两种制作电视应用程序的方法,即TVML和UIKit,并且还没有官方提及如何混合以制作TVML(基本上是XML)用户界面和本机计数器部分.应用程序逻辑和I/O(例如播放,流式传输,iCloud持久性等).
So far tvOS
supports two ways to make tv apps, TVML and UIKit, and there is no official mentions about how to mix up things to make a TVML (that is basically XML) User Interface with the native counter part for the app logic and I/O (like playback, streaming, iCloud persistence, etc).
那么,在新的tvOS
应用程序中混合TVML
和UIKit
的最佳解决方案是什么?
So, which is the best solution to mix TVML
and UIKit
in a new tvOS
app?
在下面,我尝试了以下解决方案,这些解决方案是根据Apple论坛和与JavaScriptCore到ObjC/Swift绑定相关的问题而改编的.这是Swift项目中的一个简单包装器类.
In the following I have tried a solution following code snippets adapted from Apple Forums and related questions about JavaScriptCore to ObjC/Swift binding.This is a simple wrapper class in your Swift project.
import UIKit
import TVMLKit
@objc protocol MyJSClass : JSExport {
func getItem(key:String) -> String?
func setItem(key:String, data:String)
}
class MyClass: NSObject, MyJSClass {
func getItem(key: String) -> String? {
return "String value"
}
func setItem(key: String, data: String) {
print("Set key:\(key) value:\(data)")
}
}
代表必须符合TVApplicationControllerDelegate
的地方:
typealias TVApplicationDelegate = AppDelegate
extension TVApplicationDelegate : TVApplicationControllerDelegate {
func appController(appController: TVApplicationController, evaluateAppJavaScriptInContext jsContext: JSContext) {
let myClass: MyClass = MyClass();
jsContext.setObject(myClass, forKeyedSubscript: "objectwrapper");
}
func appController(appController: TVApplicationController, didFailWithError error: NSError) {
let title = "Error Launching Application"
let message = error.localizedDescription
let alertController = UIAlertController(title: title, message: message, preferredStyle:.Alert ) self.appController?.navigationController.presentViewController(alertController, animated: true, completion: { () -> Void in
})
}
func appController(appController: TVApplicationController, didStopWithOptions options: [String : AnyObject]?) {
}
func appController(appController: TVApplicationController, didFinishLaunchingWithOptions options: [String : AnyObject]?) {
}
}
这时javascript非常简单.看一下带有命名参数的方法,您将需要更改javascript计数器部分的方法名称:
At this point the javascript is very simple like. Take a look at the methods with named parameters, you will need to change the javascript counter part method name:
App.onLaunch = function(options) {
var text = objectwrapper.getItem()
// keep an eye here, the method name it changes when you have named parameters, you need camel case for parameters:
objectwrapper.setItemData("test", "value")
}
App. onExit = function() {
console.log('App finished');
}
现在,假设您有一个非常复杂的js界面要导出,就像
Now, supposed that you have a very complex js interface to export like
@protocol MXMJSProtocol<JSExport>
- (void)boot:(JSValue *)status network:(JSValue*)network user:(JSValue*)c3;
- (NSString*)getVersion;
@end
@interface MXMJSObject : NSObject<MXMJSProtocol>
@end
@implementation MXMJSObject
- (NSString*)getVersion {
return @"0.0.1";
}
你可以喜欢
JSExportAs(boot,
- (void)boot:(JSValue *)status network:(JSValue*)network user:(JSValue*)c3 );
这时在JS Counter部分中,您将不会进行驼峰式的情况:
At this point in the JS Counter part you will not do the camel case:
objectwrapper.bootNetworkUser(statusChanged,networkChanged,userChanged)
但是您要这样做:
objectwrapper.boot(statusChanged,networkChanged,userChanged)
最后,再次查看此界面:
Finally, look at this interface again:
- (void)boot:(JSValue *)status network:(JSValue*)network user:(JSValue*)c3;
传入的值JSValue *是在ObjC/Swift
和JavaScriptCore
之间传递完成处理程序的一种方式.此时,在本机代码中,您都使用参数进行了调用:
The value JSValue* passed in. is a way to pass completion handlers between ObjC/Swift
and JavaScriptCore
. At this point in the native code you do all call with arguments:
dispatch_async(dispatch_get_main_queue(), ^{
NSNumber *state = [NSNumber numberWithInteger:status];
[networkChanged.context[@"setTimeout"]
callWithArguments:@[networkChanged, @0, state]];
});
在我的发现中,我看到如果您不在主线程上分派和异步,则MainThread将挂起.因此,我将调用调用完成处理程序回调的javascript"setTimeout"调用.
In my findings, I have seen that the MainThread will hang if you do not dispatch on the main thread and async. So I will call the javascript "setTimeout" call that calls the completion handler callback.
所以我在这里使用的方法是:
So the approach I have used here is:
- 使用
JSExportAs
调用带有命名参数的方法,避免使用驼峰式的JavaScript对应语言,例如callMyParam1Param2Param3 - 使用
JSValue
作为参数来摆脱完成处理程序.在本机端使用callWithArguments.在JS端使用javascript函数; -
dispatch_async
用于完成处理程序,可以在JavaScript端调用延迟为0的setTimeout,以避免UI冻结.
- Use
JSExportAs
to take car of methods with named parameters and avoid to camel case javascript counterparts like callMyParam1Param2Param3 - Use
JSValue
as parameter to get rid of completion handlers. Use callWithArguments on the native side. Use javascript functions on the JS side; dispatch_async
for completion handlers, possibly calling a setTimeout 0-delayed in the JavaScript side, to avoid the UI to freeze.
[UPDATE] 我已经更新了这个问题,以使其更加清楚.我正在寻找一种桥接TVML
和UIKit
以便
[UPDATE]I have updated this question in order to be more clear. I'm finding a technical solution for bridging TVML
and UIKit
in order to
- 使用
JavaScriptCode
了解最佳编程模型 - 具有从
JavaScriptCore
到ObjectiveC
的正确桥,并且反之亦然 - 从
Objective-C
调用
JavaScriptCode
时具有最佳性能- Understand the best programming model with
JavaScriptCode
- Have the right bridge from
JavaScriptCore
toObjectiveC
andviceversa - Have the best performances when calling
JavaScriptCode
fromObjective-C
推荐答案
此 WWDC视频解释了如何在JavaScript和Obj-C之间进行通信
This WWDC Video explains how to communicate between JavaScript and Obj-C
这是我从Swift到JavaScript的通信方式:
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
})
}
这是我从JavaScript到Swift的通信方式(需要在Swift中进行一些设置):
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] 对于那些想了解"pushAlert"在javascript方面是什么样的人,我将分享在application.js中实现的示例
[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
}
这篇关于如何将TVML/JavaScriptCore桥接到UIKit/Objective-C(Swift)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!