LoginSignup
11
4

More than 5 years have passed since last update.

TVML, TVMLKit JS と swift を連携しよう

Last updated at Posted at 2017-12-09

TVML と TVMLKit JS

みなさま、TVML, TVMLKit JS を使ってアプリ作ってますか?

tvOS向けにアプリを開発しているデベロッパの方は、キックオフ時に一度、TVMLを採用するかどうかを悩むタイミングがあると思います(ありますよね...?)

TVMLを採用した時のメリット/デメリットはいろんなところで語られていると思いますが、大きくはこんなところかと思います。

  • 公式っぽいUIコンポーネントが本当に簡単に作れる
  • TVMLやJSなどのサーバサイドモジュールは、申請なしで改修できる
  • ちょっと凝ったUIにしようとしたらかなり大変

テンプレートやUIコンポーネントは非常に強力だったりするのですが、自由なカスタムができないので、複雑なアプリには向かなかったりします。

tvOS11.0からは、TVMLKit JS に Data Binding 用のAPIが追加されたりしているのですが、癖が強くてとっつきにくかったりします。

とはいえ、第5世代のAppleTVもリリースされましたし、世の中的には動画配信サービスも有料・無料を問わず色々と流行っていますし、ちょっとTVMLに触ってみるのもいいかもしれないですね。

swiftの実装を呼び出す

TVMLで実装する場合、ロジックは基本的に TVMLKit JS で書きます。

TVMLKit JS からでもUserDefaultsやデバイス情報へのアクセスなどの簡単な処理はできるのですが、ネイティブ向けに用意されたAPIやフレームワークには、直接アクセスはできません。

つまり、課金処理やゲームセンター、キーチェーン領域へのアクセスを行うのであれば、swiftで実装を行い、TVMLやTVMLKit JS からそれらの処理を呼び出すことになります。

AppDelegateで登録する

swiftをJSから呼び出せるようにするには、あらかじめAppDelegateで登録しておく必要があります。処理の登録には、クロージャ単位で処理を登録する方法と、クラス単位で登録する方法があります。

クロージャ単位で登録する

簡単な処理であれば、直接クロージャを登録してしまうのもありかと思います。

AppDelegate.swift
// TVApplicationControllerDelegateのメソッドを実装する
func appController(_ appController: TVApplicationController, evaluateAppJavaScriptIn jsContext: JSContext) {
    // 文字列を受け取って出力するだけのクロージャ.
    let closure: @convention(block) (String) -> Void = {
        print($0)
    }
    // "log"という名前でクロージャを登録する.
    jsContext.setObject(closure, forKeyedSubscript: "log" as NSString)
}

TVMLやTVMLKit JS を利用して、登録した処理を呼び出します。

application.js
// 登録したクロージャを呼ぶためのfunction.
function logWithSwift() {
    // 登録したクロージャを呼び出す.
    log("jsからswiftを呼ぶよ")
}
HelloWorld.xml
<document>
    <alertTemplate>
        <title>Swiftを呼ぶよ</title>
        <button onselect="logWithSwift()">
            <text>logWithSwift()</text>
        </button>
    </alertTemplate>
</document>

クラス単位で登録する

実際には、クラスに処理をまとめて、クラスごと登録する方が実用的です。
まずは JSExport を継承したプロトコルを定義し、そこにJSから呼び出したい処理のI/Fを定義します。

AuthManagerProtocol.swift
import TVMLKit

@objc protocol AuthManagerProtocol: JSExport {
    func setUserName(_ userName: String)
    func userName() -> String
}

作成したプロトコルを実装したクラスを作成し、実処理を定義します。

AuthManager.swift
import UIKit

class AuthManager: NSObject, AuthManagerProtocol {
    // ユーザ名の保存処理.
    func setUserName(_ userName: String) {
        let defaults = UserDefaults.standard
        defaults.set(userName, forKey: "USER_NAME")
        defaults.synchronize()
    }
    // ユーザ名の取得処理.
    func userName() -> String {
        let defaults = UserDefaults.standard
        return defaults.string(forKey: "USER_NAME") ?? "(no user)"
    }
}

作成したクラスは、先ほどと同様にAppDelegateで登録します。

AppDelegate.swift
// TVApplicationControllerDelegateのメソッドを実装する
func appController(_ appController: TVApplicationController, evaluateAppJavaScriptIn jsContext: JSContext) {
    // ...

    // "authManager"という名前で、クラス単位で登録する.
    jsContext.setObject(AuthManager(), forKeyedSubscript: "authManager" as NSString)
}

登録した処理を TVML や TVMLKit JS から呼び出せるようにします。

application.js
// ユーザ名の保存処理を呼び出す.
function setUserNameWithSwift(userName) {
    authManager.setUserName(userName);
}
// ユーザ名の取得処理を呼び出す.
function userNameWithSwift() {
    const userName = authManager.userName();
    log(`userName = ${userName}`);
}
HelloWorld.xml
<document>
    <alertTemplate>

        ...省略

        <button onselect="setUserNameWithSwift('Susumu Hoshikawa')">
            <text>ユーザ名の保存処理</text>
        </button>
        <button onselect="userNameWithSwift()">
            <text>ユーザ名の取得処理</text>
        </button>
    </alertTemplate>
</document>

まとめ

TVMLを採用した場合でも、多くの場合はswift層での実装も必要になると思います。
今回はJSからswiftを呼び出していますが、実際にはswiftからのコールバックをJSに渡す必要も出てくると思います。

このようにJSとswiftの連携は必須処理になると思いますので、処理の分担をしっかり設計した上で実装を行いましょう!

TVMLを採用する案件が増えて、世の中にもっと情報が出回るといいなぁ...

今回作成したプロジェクトはこちらです。
https://github.com/hoshi005/TVML-sample

11
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
4