調べているtvOS
と、Apple がを使用して作成された素敵なテンプレートTVML
セットを提供していることがわかりました。テンプレートを使用するtvOS
アプリでも使用できるかどうかを知りたいです。TVML
UIKit
1 つのアプリ内で UIKit と TVMLKit を混在させることはできますか?
Apple Developer Forumでスレッドを見つけましたが、この質問に完全には答えていないため、ドキュメントを調べて答えを見つけています。
はい、できます。TVML テンプレートを表示するには、JavaScript コンテキストを制御するオブジェクトTVApplicationControllerを使用する必要があります。
var appController: TVApplicationController?
このオブジェクトには、関連付けられたUINavigationControllerプロパティがあります。したがって、必要に応じていつでも呼び出すことができます。
let myViewController = UIViewController()
self.appController?.navigationController.pushViewController(myViewController, animated: true)
これにより、カスタム UIKit ビューコントローラーをナビゲーション スタックにプッシュできます。TVML テンプレートに戻りたい場合は、viewController をナビゲーション スタックからポップするだけです。
JavaScript と Swift の間の通信方法について知りたい場合は、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 でcreatePushMyView()を呼び出すと、JavaScript コードでpushMyView()を自由に呼び出すことができ、View Controller がスタックにプッシュされます。
スイフト 4.1 アップデート
メソッド名とキャストにいくつかの簡単な変更を加えるだけです。
appController?.evaluate(inJavaScriptContext: {(evaluation: JSContext) -> Void in
と
evaluation.setObject(unsafeBitCast(pushMyViewBlock, to: AnyObject.self), forKeyedSubscript: "pushMyView" as NSString)
受け入れられた回答で述べたように、JavaScript コンテキスト内からほとんどすべての Swift 関数を呼び出すことができます。名前が示すように、setObject:forKeyedSubscript:
はブロックに加えてオブジェクト (JSExport から継承するプロトコルに準拠している場合) も受け入れ、そのオブジェクトのメソッドとプロパティにアクセスできることに注意してください。これが例です
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]
}
}
次に、アプリ コントローラーで次のようにします。
func appController(appController: TVApplicationController, evaluateAppJavaScriptInContext jsContext: JSContext) {
let bridge:JSBridge = JSBridge();
jsContext.setObject(bridge, forKeyedSubscript:"bridge");
}
次に、JS でこれを行うことができます。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
}
}
残念ながら、view/viewControllerForElement: はすべての要素に対して呼び出されるわけではありません。一部の既存の要素 (コレクション ビューなど) は、インターフェイス ファクトリを使用せずに子要素自体のレンダリングを処理します。つまり、より高いレベルの要素をオーバーライドするか、カテゴリ/スウィズリングまたは UIAppearance を使用して取得する必要があります。あなたが望む効果。
最後に、先ほど暗示したように、UIAppearance を使用して、特定の組み込みビューの外観を変更できます。TVML アプリのタブ バーの外観を変更する最も簡単な方法は次のとおりです。たとえば、次のようになります。
// in didFinishLaunching...
UITabBar.appearance().backgroundImage = UIImage()
UITabBar.appearance().backgroundColor = UIColor(white: 0.5, alpha: 1.0)
tvOS 用のネイティブ UIKit アプリが既にあるが、その一部に TVMLKit を使用して拡張したい場合は、それが可能です。
TVMLKit をネイティブ tvOS アプリのサブアプリとして使用します。次のアプリは、 を保持し、からTVApplicationController
を提示することで、これを行う方法を示しています。URL がここに転送されるため、JavaScript アプリにデータを転送するために使用されます。navigationController
TVApplicationController
TVApplicationControllerContext
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)
}
はい。ドキュメントが次で始まるTVMLKit Frameworkを参照してください。
TVMLKit フレームワークを使用すると、JavaScript および TVML ファイルをバイナリ アプリに組み込み、クライアント サーバー アプリを作成できます。
これらのドキュメントをざっと見てみると、さまざまなTVWhateverFactory
クラスを使用して TVML から UIKit ビューまたはビュー コントローラーを作成し、その後それらを UIKit アプリに挿入できるように見えます。