[Xcode 8.1] サンプルコードからTouchBarのAPIを理解する#2 NSTouchBar Catalogの初期画面でやっていること

2016.11.03

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

本記事の目標:ButtonViewController.swiftでやっていることを理解する

こんにちは!モバイルアプリサービス部の加藤潤です。
前回に引き続きNSTouchBar Catalogを理解していきます。
今回はサンプルアプリを起動すると最初に表示される画面(ButtonViewController.swift)で何をやっているかを見ていきたいと思います。
(※実は前回の記事の実行結果に載せた画面キャプチャはWindowController.swiftで何をやっているかを理解しやすくするためにMasterViewController.swiftの処理の大半をコメントアウトして実行した結果でした。)

実行結果

先にアプリとタッチバー、カスタマイズパレットの実行結果を載せておきます。

アプリとタッチバー

ButtonViewController

「Size Constraint」にチェックを入れると「Button 3」と書かれたボタンの幅が大きくなります。 また、「Custom Button Color」にチェックを入れると3つのボタンの色が黄色になります。ボタンのタイトルも変化しています。

カスタマイズパレット

ButtonViewController_customize_palette

「Catalog」と書かれたアイテムとその隣のスペースアイテムは削除したり、配置の入れ替えができますが、3つのボタンについてはそれができない作りになっています。

3つのボタンを持つタッチバーはStoryboardで定義されている

3つのボタンを持つタッチバーは以下のようにButtonViewController.storyboardで定義されています。 ButtonViewController.storyboard

コードを読み解く

ButtonViewController.swift

ButtonViewController.storyboardの中でInitial Controllerとして定義されている、NSViewControllerのサブクラスであるButtonViewControllerクラスのソースコードは以下のようになっています。

import Cocoa

class ButtonViewController: NSViewController {

    @IBOutlet weak var sizeConstraint: NSButton!

    @IBOutlet weak var useCustomColor: NSButton!

    @IBOutlet weak var button3: NSButton!

    lazy var button3Constraints: [NSLayoutConstraint] = {
        return NSLayoutConstraint.constraints(withVisualFormat: "H:[button3(200)]",
                                              options: [],
                                              metrics: nil,
                                              views: ["button3": self.button3!])
    }()

    // MARK: - Action Functions

    @IBAction func customize(_ sender: AnyObject) {
        guard let touchBar = self.touchBar else { return }

        for itemIdentifier in touchBar.itemIdentifiers {

            guard let item = touchBar.item(forIdentifier: itemIdentifier) as? NSCustomTouchBarItem,
                let button = item.view as? NSButton else {continue}

            let textRange = NSRange(location: 0, length: button.title.characters.count)
            let titleColor = useCustomColor.state == NSOnState ? NSColor.black : NSColor.white

            let newTitle = NSMutableAttributedString(string: button.title)
            newTitle.addAttribute(NSForegroundColorAttributeName, value: titleColor, range: textRange)
            newTitle.addAttribute(NSFontAttributeName, value: button.font!, range: textRange)
            newTitle.setAlignment(.center, range: textRange)

            button.attributedTitle = newTitle
            button.bezelColor = useCustomColor.state == NSOnState ? NSColor.yellow : nil
        }

        if sizeConstraint.state == NSOnState {
            NSLayoutConstraint.activate(button3Constraints)
        }
        else {
            NSLayoutConstraint.deactivate(button3Constraints)
        }
    }

    @IBAction func buttonAction(_ sender: AnyObject) {
        print("\(#function): button with title \"\((sender as! NSButton).title)\" is tapped!")
    }
}

20〜21行目

20行目から始まるアクションメソッドは、「Size Constraint」、「Custom Button Color」のどちらのチェック状態が変化しても呼ばれます。
このメソッド内の処理で特筆すべきところは、self.touchBarでstoryboardで定義したタッチバーのインスタンスが取得できるところでしょうか。このプロパティの定義はNSResponderにありますが、NSViewControllerNSResponderを継承していて、かつstoryboard上でタッチバーを階層的にButtonViewControllerの中に配置しているためにこのプロパティで取得できているのでしょう。

23行目

itemIdentifiersプロパティでバー内のアイテムIDを取得してループ処理をしています。このプロパティは読み取り専用プロパティで、ユーザーによってバーがカスタマイズされていない場合はdefaultItemIdentifiersと同じアイテムIDの配列を返します。

25〜26行目

touchBar.item(forIdentifier: itemIdentifier)の部分でアイテムIDを指定してNSTouchBarItemを取得し、更に今回はアイテム内のビューとしてNSButtonを配置しているため、NSCustomTouchBarItemにキャストしています。 NSCustomTouchBarItemにキャストすることで、viewプロパティを通じてNSButtonを取得しています。

28〜45行目

ボタンのattributedTitlebezelColorを設定しています。bezelColorは「Custom Button Color」のチェックありの場合はyellow、チェックなしの場合はnilを設定しています。
また、「Size Constraint」のチェックありの場合はbutton3Constraintsの制約を有効に、チェックなしの場合は無効にしていますね。 button3ConstraintsはVFL(Visual Format Language)で定義されたAutoLayoutの制約です。
制約の内容は「button3の横幅を200にする」です。VFLについてはこちらをご覧ください。

おわりに

今回はButtonViewController.swiftのコードから、storyboardで定義されたタッチバーをコードから取得してカスタマイズする方法を学びました。 まだまだ奥が深いタッチバーです。

参考