[Xcode 8.1] サンプルコードからTouchBarのAPIを理解する#1 TouchBarの概要とサンプルコードの動かし方

2016.10.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

新しいMacBook Pro出ましたね

こんにちは!モバイルアプリサービス部の加藤 潤です。
先日発表された新しいMacBook ProにはTouch Barと呼ばれる新しい入力デバイスが搭載されました。 一時、「escキーが無くなったらどうしよう...?」と話題になりましたが、escキーが使えなくなるわけではない事がわかりホッとした人も多いかと思います。 さて、このTouch Barですが、現在使っているアプリ(コンテキスト)によって表示するコントロールを変えられるということで、今回はAppleのサンプルコードからTouch BarのAPIの使い方を見ていきたいと思います。

まずはTouch Barについて知る

まずはTouch Barについて概要を理解しましょう。こういう時は公式ドキュメントを読むのが一番です。macOS Human Interface Guidelines を見て見ます。

Touch Barとは

上記公式ドキュメントによると、Touch Barとは、「(Touch Barを)サポートしているMacBook Proのモデルでキーボードの上に表示されるRetinaディスプレイで、メインスクリーン上のコンテンツとやり取りを行うための動的なインターフェースコントロールを提供する入力デバイス」と書かれています。つまり、その時の状況に応じて適切なコントロールがRetinaディスプレイに表示され、かつディスプレイなのでタッチで操作できると読み取れます。
具体的な使用例として以下のようなケースが挙げられています。

  • ドキュメント作成時のテキスト入力中にフォントやフォントサイズを変える
  • 地図を表示中にワンタップで近くのガソリンスタンドやホテル、レストランにアクセスする
  • Touch IDセンサーによるログインやApp StoreやApple Payの支払い

ここまで読んだだけでもなんだか便利そうですね!

Touch Barの構成要素

引き続きドキュメントを見てみると、ドキュメントにはこうあります。
「デフォルトでは、Touch Barの右側にはSiriの起動やメインディスプレイの明るさ調整、ボリューム調整など、システムレベルのタスクを実行するためのコントロールが表示されます。」
「システムレベルのコントロールの左横のアプリ領域内にアプリ固有のコントロールを実装することができます」
「Escボタンやその他のシステムによって提供されるボタンはコンテキストによってアプリ領域の表示されます。」
つまり、Touch Barには以下のそれぞれの用途用に以下の領域があるようです。

  • System button
    • Escボタンやその他のシステムによって提供されるボタンを表示する領域
  • App region
    • アプリ固有のコントロールを表示する領域
  • Control Strip
    • Siriの起動やメインディスプレイの明るさ調整など、システムレベルのタスクを実行するためのコントロールが表示される領域

さらに、Touch Barはユーザーが特定のコントロールを非表示にしたり、追加したりカスタマイズできると書かれています。

ここまででざっくりとではありますが、Touch Barについて理解できました。

Appleのサンプルコード

AppleからTouch BarのサンプルとしてNSTouchBar Catalog が提供されています。 こちらを読んでAPIについて理解したいと思います。

開発環境

サンプルコードを動かすための環境は以下の通りです。

  • システムのバージョン macOS 10.12.1 (16B2657)
  • Xcode Version 8.1 (8B62)

App StoreからインストールしたmacOS 10.12.1(ビルドバージョン 16B2555)ではサンプルコードがクラッシュしてしまい動作しませんでした。サンプルコードを動かすにはこちらのアップデートが必要です。アップデートを適用すると、ビルドバージョンが16B2657となります。また、後述するTouch Barのシミュレータもビルドバージョンが16B2657以降でないと使えません。この件に関してはXcode 8.1 Release Notesにも記載されており、Touch Barを使ったアプリを開発する場合、このアップデートは必須です。

Xcode でTouch Barのシミュレータを表示するには

XcodeでTouch Barのシミュレータを表示するにはWindow > Show Touch Barを選択します。 show_touch_bar

選択すると以下のようなシミュレータが表示されます。 このスクリーンショットはちょうど画面キャプチャ時のものです。ちゃんとコンテキストに応じたコントロールが表示されることが確認できました。

touch_bar_simulator

サンプルコードを読み解く

AppDelegate.swift

AppDelegate.swiftの処理は以下のようになっています。

if NSClassFromString("NSTouchBar") != nil {
    NSApplication.shared().isAutomaticCustomizeTouchBarMenuItemEnabled = true
}

NSClassFromString("NSTouchBar") != nil の部分でTouch Barの機能が使えるかどうかを実行時に確認しています。そしてNSApplication.shared().isAutomaticCustomizeTouchBarMenuItemEnabledtrueを設定することでアプリのメインメニューに「Customize Touch Bar」メニューを表示しています。試しにfalseに設定したら当該メニューは表示されませんでした。

customize_touch_bar_menu

WindowController.swift

WindowController.swiftの処理は以下のようになっています。

fileprivate extension NSTouchBarCustomizationIdentifier {
    static let windowBar = NSTouchBarCustomizationIdentifier("com.TouchBarCatalog.windowTouchBar")
}

fileprivate extension NSTouchBarItemIdentifier {
    static let label = NSTouchBarItemIdentifier("com.TouchBarCatalog.TouchBarItem.label")
}

class WindowController: NSWindowController {

    override func windowDidLoad() {
        super.windowDidLoad()

        self.window?.setFrameAutosaveName("WindowAutosave")
    }

    // MARK: NSTouchBar

    override func makeTouchBar() -> NSTouchBar? {
        let touchBar = NSTouchBar()
        touchBar.delegate = self
        touchBar.customizationIdentifier = .windowBar
        touchBar.defaultItemIdentifiers = [.label, .fixedSpaceLarge, .otherItemsProxy]
        touchBar.customizationAllowedItemIdentifiers = [.label]

        return touchBar
    }

}

NSResponderに定義されているmakeTouchBar() -> NSTouchBar?という関数をオーバライドし、その中で以下を行なっています。

  1. NSTouchBarの生成
  2. 1.で生成したオブジェクトのデリゲートをself(WindowController)に設定
  3. customizationIdentifierプロパティの型はNSTouchBarCustomizationIdentifier?でTouch Barをカスタマイズ可能にするための識別子を設定します。
    • ここで設定している.windowBarは自分で定義したNSTouchBarCustomizationIdentifierです。(この識別子はグローバルにユニークである必要があるため、逆ドメイン形式にしているようです。)
  4. defaultItemIdentifiersプロパティの型は[NSTouchBarItemIdentifier]となっており、Touch Bar内の各アイテムに対する識別子を設定しています。
    • .labelは自分で定義したNSTouchBarItemIdentifierです。(この識別子もグローバルにユニークである必要があるため、逆ドメイン形式にしているようです。)
    • .fixedSpaceLargeは標準で用意された識別子で名前から固定長の大きいスペースを表すことが想像できます。(.flexibleSpaceというのもあるのでおそらくこちらは可変長のスペースを表すのでしょう)
    • .otherItemsProxyも標準で用意された識別子ですが、こちらはTouch Barを入れ子にするために必要なようです。
  5. customizationAllowedItemIdentifiersプロパティの型も[NSTouchBarItemIdentifier]です。ここで指定した識別子のアイテムはカスタマイズのパレットに表示されるようです。

そして、NSTouchBarDelegateの実装は以下のようになっています。

// MARK: NSTouchBarDelegate
extension WindowController: NSTouchBarDelegate {

    func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem? {

        switch identifier {
        case NSTouchBarItemIdentifier.label:
            let custom = NSCustomTouchBarItem(identifier: identifier)
            custom.customizationLabel = "TouchBar Catalog Label"

            let label = NSTextField.init(labelWithString: "Catalog")
            custom.view = label

            return custom

        default:
            return nil
        }
    }
}

アイテムの識別子が.labelの場合にNSCustomTouchBarItemを生成し、中のビューとしてNSTextFieldを代入しています。 この部分でアプリ固有のアイテムを定義しているようです。

実行結果

今回ご紹介したコード部分を実行した結果、Touch Barは以下のようになりました。 run_touch_bar_sample_001

また、「Customize Touch Bar」メニューをクリックして表示したカスタマイズのパレットは以下のようになりました。 touch_bar_sample_001_customization _palette Catalogと表示されたアイテムはカスタマイズ可能なので非表示にできたり、位置を変えたりできました。fixedSpaceLargeのアイテムについては非表示にはできましたが、ドラッグして位置を入れ替えることはできませんでした。(ただし、Catalogアイテムの方をドラッグすれば位置を入れ替えることが可能) defaultItemIdentifiersに設定したアイテムがカスタマイズパレット上ではDefault Setとして表示され、いろいろとカスタマイズしてもいつでもTouch Barの状態をこのデフォルトに戻すことができるようです。

ちなみに、customizationIdentifierに何も設定しないと、isAutomaticCustomizeTouchBarMenuItemEnabledtrueを設定してもアプリのメインメニューに表示された「Customize Touch Bar」メニューがdisabled状態になり、クリックできない(=カスタマイズできない)状態になりました。 また、customizationAllowedItemIdentifiersについても何も設定しないと、同様に「Customize Touch Bar」メニューがdisabled状態になりました。 このことから、カスタマイズ可能なTouch Barにしたい場合はcustomizationIdentifiercustomizationAllowedItemIdentifiersの両方を設定しないといけないことがわかりました。

おわりに

今回はAppDelegate.swiftotherItemsProxy.swiftのソースコードからざっくりとやっていることを理解しました。
引き続きサンプルコードを理解してまいります。

参考