2015-10-12 38 views
19

到目前爲止tvOS支持兩種製作電視應用,TVML和UIKit的方式,並且沒有官方提及如何混合使用TVML(基本上是XML)的用戶界面和用於應用邏輯的本機計數器部分和I/O(如播放,流媒體,iCloud持久性等)。如何將TVML/JavaScriptCore橋接到UIKit/Objective-C(Swift)?

那麼,哪個是最好的解決方案,將TVMLUIKit混合在一個新的tvOS應用程序中?

在下面,我嘗試了一個解決方案,下面的代碼片段改編自Apple論壇和有關JavaScriptCore到ObjC/Swift綁定的相關問題。 這是Swift項目中的一個簡單包裝類。

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計數器部分方法名稱:

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接口,像出口

@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計數器部分這一點上,你不會做的駱駝情況:

objectwrapper.bootNetworkUser(statusChanged,networkChanged,userChanged) 

,但你要做的:

objectwrapper.boot(statusChanged,networkChanged,userChanged) 

最後,再看看這個接口:

- (void)boot:(JSValue *)status network:(JSValue*)network user:(JSValue*)c3; 

值JSValue *傳遞的是一種方式來傳遞ObjC/Swift之間完成處理和JavaScriptCore。在本機代碼這一點你都帶參數調用:

dispatch_async(dispatch_get_main_queue(), ^{ 
              NSNumber *state = [NSNumber numberWithInteger:status]; 
              [networkChanged.context[@"setTimeout"] 
              callWithArguments:@[networkChanged, @0, state]]; 
             }); 

在我的發現,我已經看到,如果你沒有在主線程和異步的調度MainThread將掛起。所以我會調用調用完成處理程序回調的javascript「setTimeout」調用。

所以我在這裏使用的方法是:

  • 使用JSExportAs採取與命名參數方法的汽車,並避免給JavaScript同行一樣callMyParam1Param2Param3
  • 使用JSValue作爲參數擺脫完成的駱駝處理程序。在本機端使用callWithArguments。在JS端使用javascript函數;
  • dispatch_async對於完成處理程序,可能會調用setTimeout 0在JavaScript端延遲,以避免UI凍結。

[更新] 我已經爲了更新這個問題更加清晰。我發現一個技術解決方案,以彌合TVMLUIKit

  • 瞭解最佳的編程模型JavaScriptCode
  • 有從右邊橋JavaScriptCoreObjectiveC和 反之亦然
  • 有最好的表現時,打電話給JavaScriptCodeObjective-C
+1

據我所知,這不是一個問題。如果你發現了一些你想分享的有用信息,請[提問並回答你自己的問題](http://stackoverflow.com/help/self-answer)。另外,我認爲這個話題已經在[tag:tvos]或[tag:apple-tvos]的某處出現了,所以你可能不需要問一個新問題,只需回答一個問題。 – rickster

+1

@rickster如果我會找到這個問題的答案,我會回答它,但到目前爲止沒有。沒有關於「tvOS」和「TVML + UIKIT」的具體問題,所以我不明白你的觀點。是的,也許問題不清楚,我可以指定更好。你的答案是「不具有建設性」,因爲'''tvOS'''是一種全新的技術,對Stackoverflow知之甚少。當然,這是我的觀點,我敢肯定,無論如何這個問題是「建設性的」。 – loretoparisi

+3

如果這篇文章不是試圖提供信息,而且實際上是一個問題......目前還不清楚你想問什麼。也許你可以編輯,使問題更清楚。 – rickster

回答

0

您引發了一個幾乎可以工作的想法。一旦顯示了本地視圖,目前還沒有簡單的方法可以將基於TVML的視圖推送到導航堆棧上。我已經在這個時候做的是:

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

...然後在JavaScript端:

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

這似乎是移動執行關閉主線程和到JSVirtualMachine最徹底的方法線程並避免UI鎖定。請注意,我必須至少彈出當前的本地視圖控制器,否則它會發送一個致命的選擇器。

+0

因此,您在JavaScriptCore虛擬機中移動了setTimeout回調函數,而不是像上面那樣從本機對象中調用它。對,我認爲這是一個很好的選擇。爲什麼'''evaluateScript'''而不是''callWithArguments'''? – loretoparisi

15

WWDC Video介紹如何JavaScript和對象 - 之間的溝通

這裏是我從斯威夫特溝通的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傳遞給斯威夫特(它需要一些在斯威夫特設置):

//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 
}) 
} 

[更新]對於那些你們誰願意禮可以知道「pushAlert」在javascript方面會是什麼樣子,我將分享一個在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 
} 
+0

請問您可以發佈javascript代碼如何查找「pushAlert」。它也在你的presenter.js中? –

+0

@ChrisBrasino如果您在Apple的Catalog示例中使用了application.js,presenter.js和resourceloader.js,我會在application.js中聲明「pushAlert」。 – shirefriendship

+0

謝謝你會給它一個鏡頭! –