2015-10-23 5 views
17

私はtvOSを調べており、Appleはを使って書いたtemplatesの素敵なセットを提供していることがわかりました。私はtvOSTVMLテンプレートを利用するアプリもUIKitを使用できるかどうかを知りたいと思います。1つのアプリケーションでUIKitとTVMLKitを混在させることはできますか?

UIKitとTVMLKitを1つのアプリケーションで混在させることはできますか?

私はApple Developer Forumにスレッドを見つけましたが、この質問には完全には答えていません。答えを見つけるためにドキュメントを調べています。

答えて

31

はい、できます。 TVMLテンプレートを表示するには、JavaScriptコンテキストを制御するオブジェクト(TVApplicationController)を使用する必要があります。

var appController: TVApplicationController? 

このオブジェクトは、それに関連付けられたUINavigationController性質を有しています。あなたが合うたびだから、あなたが呼び出すことができます。これはあなたがナビゲーションスタックにカスタムのUIKitのViewControllerをプッシュすることができます

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

。 TVMLテンプレートに戻る場合は、viewControllerをナビゲーションスタックから外してください。何を知りたいのですが、JavaScriptとスウィフトの間で通信する方法であれば

は、ここにあなたがcreatePushMyViewを呼び出したら、()pushMyViewを(と呼ばれるjavascript関数)

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

を作成する方法であり、 Swiftでは、JavaScriptコードにpushMyView()を自由に呼び出すことができ、ビューコントローラをスタックにプッシュします。

+2

素晴らしい!これは、この文脈でJSとSwiftとの間のインタラクションなど、質問された質問より多くの質問に答えました。この例のようなすべてのjavascript関数は、application.jsを呼び出す前に挿入する必要があります。公正? –

+0

@FrankC。私はこれがあなたを助けてくれてうれしいです!はい、このメソッドは基本的にコンテキストにjavascriptを注入するので、pushMyView()を呼び出す前にいつかcreatePushMyView()を呼び出す必要があります。 SwiftでcreatePushMyView()を呼び出すことなくJS内でpushMyView()を呼び出そうとすると、未定義のオブジェクトを呼び出しても機能しません。 – shirefriendship

+0

@shirefriendshipこれは素晴らしい答えです、ありがとうございます。このプッシュ機能は、あらかじめJSバックエンド側でそのような機能が既に作成されているはずだと説明したもののほんの一例に過ぎないことを指摘したいだけです。他の例やチュートリアルからもわかるように。 – Placeable

-1

はい。そのドキュメントで始まるTVMLKit Frameworkを参照してください:

TVMLKitフレームワークは、クライアントサーバーアプリケーションを作成するために、バイナリのアプリケーションでJavaScriptとTVMLファイルを組み込むことができます。

これらのドキュメントの迅速なスキムミルクから、あなたはTVMLからのUIKitビューまたはビューコントローラを作成するために、様々なTVWhateverFactoryクラスを使用するように、それはあなたがUIKitのアプリにそれらを挿入することができた後に、見えます。

5

受け入れられた回答に記載されているとおり、JavaScriptのコンテキスト内でSwift関数を呼び出すことができます。この名前が示すように、setObject:forKeyedSubscript:はブロックに加えてオブジェクト(JSExportを継承するプロトコルに準拠する場合)を受け入れ、そのオブジェクトのメソッドやプロパティにアクセスできるようにします。あなたのJSに

func appController(appController: TVApplicationController, evaluateAppJavaScriptInContext jsContext: JSContext) { 
    let bridge:JSBridge = JSBridge(); 
    jsContext.setObject(bridge, forKeyedSubscript:"bridge"); 
} 

は、その後、あなたがこれを行うことができます:ここでは例アプリのコントローラで次に

import Foundation 
import TVMLKit 

// Just an example, use sessionStorage/localStorage JS object to actually accomplish something like this 
@objc protocol JSBridgeProtocol : JSExport { 
    func setValue(value: AnyObject?, forKey key: String) 
    func valueForKey(key: String) -> AnyObject? 
} 

class JSBridge: NSObject, JSBridgeProtocol { 
    var storage: Dictionary<String, String> = [:] 
    override func setValue(value: AnyObject?, forKey key: String) { 
     storage[key] = String(value) 
    } 
    override func valueForKey(key: String) -> AnyObject? { 
     return storage[key] 
    } 
} 

bridge.setValue(['foo', 'bar'], "baz")

それだけでなく、既存の要素のためのビューを上書きすることができ、マークアップで使用するカスタム要素を定義し、ネイティブビューでそれらを戻すことができます。

// Call lines like these before you instantiate your TVApplicationController 
TVInterfaceFactory.sharedInterfaceFactory().extendedInterfaceCreator = CustomInterfaceFactory() 
// optionally register a custom element. You could use this in your markup as <loadingIndicator></loadingIndicator> or <loadingIndicator /> with optional attributes. LoadingIndicatorElement needs to be a TVViewElement subclass, and there are three functions you can optionally override to trigger JS events or DOM updates 
TVElementFactory.registerViewElementClass(LoadingIndicatorElement.self, forElementName: "loadingIndicator") 

クイックカスタム要素の例:

import Foundation 
import TVMLKit 

class LoadingIndicatorElement: TVViewElement { 
    override var elementName: String { 
     return "loadingIndicator" 
    } 

    internal override func resetProperty(resettableProperty: TVElementResettableProperty) { 
     super.resetProperty(resettableProperty) 
    } 
    // API's to dispatch events to JavaScript 
    internal override func dispatchEventOfType(type: TVElementEventType, canBubble: Bool, cancellable isCancellable: Bool, extraInfo: [String : AnyObject]?, completion: ((Bool, Bool) -> Void)?) { 
     //super.dispatchEventOfType(type, canBubble: canBubble, cancellable: isCancellable, extraInfo: extraInfo, completion: completion) 
    } 

    internal override func dispatchEventWithName(eventName: String, canBubble: Bool, cancellable isCancellable: Bool, extraInfo: [String : AnyObject]?, completion: ((Bool, Bool) -> Void)?) { 
     //... 
    } 
} 

そして、ここでは、カスタムインタフェースファクトリの設定方法は次のとおりです。残念ながら

class CustomInterfaceFactory: TVInterfaceFactory { 
    let kCustomViewTag = 97142 // unlikely to collide 
    override func viewForElement(element: TVViewElement, existingView: UIView?) -> UIView? { 

     if (element.elementName == "title") { 
      if (existingView != nil) { 
       return existingView 
      } 

      let textElement = (element as! TVTextElement) 
      if (textElement.attributedText!.length > 0) { 
       let label = UILabel()      

       // Configure your label here (this is a good way to set a custom font, for example)... 
       // You can examine textElement.style or textElement.textStyle to get the element's style properties 
       label.backgroundColor = UIColor.redColor() 
       let existingText = NSMutableAttributedString(attributedString: textElement.attributedText!) 
       label.text = existingText.string 
       return label 
      } 
     } else if element.elementName == "loadingIndicator" { 

      if (existingView != nil && existingView!.tag == kCustomViewTag) { 
       return existingView 
      } 
      let view = UIImageView(image: UIImage(named: "loading.png")) 
      return view // Simple example. You could easily use your own UIView subclass 
     } 

     return nil // Don't call super, return nil when you don't want to override anything... 
    } 

    // Use either this or viewForElement for a given element, not both 
    override func viewControllerForElement(element: TVViewElement, existingViewController: UIViewController?) -> UIViewController? { 
     if (element.elementName == "whatever") { 
      let whateverStoryboard = UIStoryboard(name: "Whatever", bundle: nil) 
      let viewController = whateverStoryboard.instantiateInitialViewController() 
      return viewController 
     } 
     return nil 
    } 


    // Use this to return a valid asset URL for resource:// links for badge/img src (not necessary if the referenced file is included in your bundle) 
    // I believe you could use this to cache online resources (by replacing resource:// with http(s):// if a corresponding file doesn't exist (then starting an async download/save of the resource before returning the modified URL). Just return a file url for the version on disk if you've already cached it. 
    override func URLForResource(resourceName: String) -> NSURL? { 
     return nil 
    } 
} 

を、/ viewControllerForElementを表示:すべての要素のために呼び出されることはありません。既存の要素(コレクションビューなど)の中には、インターフェイスファクトリを使用せずに子要素自体のレンダリングを処理するものがあります。つまり、上位要素をオーバーライドする必要があります。カテゴリ/スウィズルやUIAppearanceを使用する必要があります。あなたが望む効果。

最後に、UIAppearanceを使用して、特定の組み込みビューの外観を変更することができます。ここにあなたのTVMLアプリのタブバーの外観を変更する最も簡単な方法は、例えば、です:

// in didFinishLaunching... 
UITabBar.appearance().backgroundImage = UIImage() 
UITabBar.appearance().backgroundColor = UIColor(white: 0.5, alpha: 1.0) 
2

すでにtvOSのネイティブのUIKitアプリを持っているが、それのいくつかの部分のためのTVMLKitを使用してそれを拡張したい場合、 あなたはできる。

ネイティブtvOSアプリでサブアプリケーションとしてTVMLKitを使用します。以下のアプリは、TVApplicationControllerを保持し、TVApplicationControllerからnavigationControllerを提示することによって、これを行う方法を示しています。 TVApplicationControllerContextは、URLがここに転送されるので、JavaScriptアプリにデータを転送するために使用されます。

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) 
    } 
関連する問題