TouchBar 入门开发指南

By on

Environments / 环境要求

  • macOS Sierra 10.12.1 (16B2657)
  • Xcode 8.1 (8B62)

可以通过 https://developer.apple.com/ 获取更多配置环境信息。

Get Started / 上手实践

要实现 NSTouchBar,我们必须做下面三件事情:

  1. 继承 NSResponder
  2. 遵循 NSTouchBarProvider 协议
  3. 在 NSTouchBarProvider 协议中实现 makeTouchBar() 方法

我们都知道,NSViewController 是 NSResponder 的一个子类实现(如果对 NSResponder 的原理不了解,可以参看这篇文章)。在 NSTouchBarProvider 的定义下方,可以看到在 10.12.1 中的 NSResponder 已经遵循了 NSTouchBarProvider 协议:

public protocol NSTouchBarProvider : NSObjectProtocol {
    @available(OSX 10.12.1, *)
    public var touchBar: NSTouchBar? { get }
}

extension NSResponder : NSTouchBarProvider {
    @available(OSX 10.12.1, *)
    open var touchBar: NSTouchBar?

    @available(OSX 10.12.1, *)
    open func makeTouchBar() -> NSTouchBar?
}

所以,对于开发者来说,你要做的,就是对任何一个继承自 NSResponder 的元素,去实现 makeTouchBar() 方法。

我们来做一个最简单的 TouchBar 应用:在屏幕上随机生成两个 0 到 9 的整数,通过点击 TouchBar 中的按钮来比较两个数大小,并给出结果。完成后的效果如图:

Screenshot

Project Init / 新建工程

打开 Xcode,并新建一个 Xcode 工程。选择 macOS 上的 Cocoa Application,点击下一步,输入项目名称 TouchBarSample,并在勾选使用 Storyboards。在这里我们选择使用 Swift 3.0 开发。

Step 1

进入 Main.storyboard,删除默认的 Window Controller 和 View Controller。我们在左边的项目中新建我们的 WindowController。选择新建一个 Cocoa Class,类名为 WindowController,继承自 NSWindowController 并勾选使用 XIB 文件。

Step 2

打开 WindowController.swift,重写其 windowNibName 属性:

override var windowNibName: String? {
    return "WindowController"
}

接下来,修改在 AppDelegate.swift 中的代码,让刚刚创建的 WindowController 显示在屏幕上:

import Cocoa

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {

    var windowController: NSWindowController?

    func applicationDidFinishLaunching(_ aNotification: Notification) {
        // Insert code here to initialize your application
        let windowController = WindowController()
        windowController.showWindow(self)
        self.windowController = windowController
        
    }

    func applicationWillTerminate(_ aNotification: Notification) {
        // Insert code here to tear down your application
    }

}

再点击 WindowController.xib,拖拽组件,摆放如下图所示(一共五个 NSTextField,以及一个 NSButton):

Step 3

现在,可以转战 WindowController.swift,专注于 TouchBar 的开发了。

Implement TouchBar / 实现 TouchBar

在实现之前,先来结合文档了解一下接下来所使用到的属性和方法。

open class NSTouchBar : NSObject, NSCoding {
    // 初始化方法
    public init()
    public init?(coder aDecoder: NSCoder)

	// 用于标识自定义 TouchBar
	// 如果标识符不存在,则无法自定义 TouchBar 中的内容
    open var customizationIdentifier: NSTouchBarCustomizationIdentifier?

	// 存储 TouchBar 中不同 Item 的标识的数组
	// 分别对应了
	// 1. 可自定义的 BarItem 对象
	// 2. 不可移除的 BarItem 对象
	// 3. 默认显示的 BarItem 对象
    open var customizationAllowedItemIdentifiers: [NSTouchBarItemIdentifier]
    open var customizationRequiredItemIdentifiers: [NSTouchBarItemIdentifier]
    open var defaultItemIdentifiers: [NSTouchBarItemIdentifier]

    // 用于动态生成 BarItem
    // 需要实现 NSTouchBarDelegate 协议
    weak open var delegate: NSTouchBarDelegate?
}

其他的属性和方法在这里不再深究。通过初步了解,为了实现一个能够自定义的、最基本的 TouchBar,你需要:

  1. 给你的 TouchBar 和 Bar Item 生成标识符
  2. 告诉 TouchBar 默认会显示哪些 Bar Item
  3. 让 TouchBar 生成这些 Bar Item

给你的 TouchBar 和 Bar Item 生成标识符

打卡 WindowController.swift,在 import Cocoaclass WindowController 中间插入以下代码,用于生成 TouchBar 和 Bar Item 的标识符:

fileprivate extension NSTouchBarCustomizationIdentifier {
    static let touchBar = NSTouchBarCustomizationIdentifier("io.Cee.TouchBarSample.touchBar")
}

fileprivate extension NSTouchBarItemIdentifier {
    static let smaller = NSTouchBarItemIdentifier("io.Cee.TouchBarSample.smaller")
    static let equal = NSTouchBarItemIdentifier("io.Cee.TouchBarSample.equal")
    static let bigger = NSTouchBarItemIdentifier("io.Cee.TouchBarSample.bigger")
}

告诉 TouchBar 默认会显示哪些 Bar Item

有了这些标识符,你就可以自定义 TouchBar 中间显示的内容了。在 windowDidLoad() 方法下方实现 makeTouchBar()

// MARK: - NSTouchBar
    
@available(OSX 10.12.1, *)
override func makeTouchBar() -> NSTouchBar? {
    let touchBar = NSTouchBar()
    
    // 接下来让 WindowController 实现 NSTouchBarDelegate
    // 生成 < = > 三个比较符号
    touchBar.delegate = self 
    
    // 给 TouchBar 一个标识
    touchBar.customizationIdentifier = .touchBar
    
    // 提供默认的 Bar Item 选项
    touchBar.defaultItemIdentifiers = [.smaller, .equal, .bigger]
    touchBar.customizationAllowedItemIdentifiers = [.smaller, .equal, .bigger]
    
    return touchBar
}

让 TouchBar 生成这些 Bar Item

下面来实现 NSTouchBarDelegate。通过不同的标识(identifier)可以区别不同的 Bar Item。

extension WindowController: NSTouchBarDelegate {
    @available(OSX 10.12.1, *)
    func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem? {
        // 生成自定义的 Bar Item
        let touchBarItem = NSCustomTouchBarItem(identifier: identifier)
        
        // 根据不同标识生成不同的 title
        var title: String
        switch identifier {
            case NSTouchBarItemIdentifier.smaller: title = "<"
            case NSTouchBarItemIdentifier.equal: title = "="
            case NSTouchBarItemIdentifier.bigger: title = ">"
            default: title = ""
        }
        
        let touchBarButton = NSButton(title: title, target: self, action: #selector(compare(with:)))
        touchBarItem.view = touchBarButton

        return touchBarItem;
    }
}

我们在这里使用了自定义的 NSCustomTouchBarItem。系统同时也提供下面几种不同的 BarItem:

  • NSGroupTouchBarItem
  • NSPopoverTouchBarItem
  • NSSliderTouchBarItem
  • NSColorPickerTouchBarItem
  • NSCandidateListTouchBarItem

完成剩余部分

  • 关联组件:使用 Ctrl-drag 将界面组件拖到代码中。
@IBOutlet weak var numberA: NSTextField!
@IBOutlet weak var numberB: NSTextField!
@IBOutlet weak var resultLabel: NSTextField!
  • 实现比较方法 compare(with:)
// MARK: - Private Method

func compare(with symbol: NSButton) {
    let number1 = numberA.intValue
    let number2 = numberB.intValue
    var result: Bool
    switch symbol.title {
        case "<": result = (number1 < number2)
        case "=": result = (number1 == number2)
        case ">": result = (number1 > number2)
        default: result = false
    }
    resultLabel.stringValue = (result == true) ? "Correct" : "Wrong"
}
  • 关联 Randomize 按钮,生成随机数:
// MARK: - Button Action
    
@IBAction func randomize(_ sender: NSButton) {
    reset()
}

// MARK: - Private Method

func reset() {
    numberA.intValue = randomAInt()
    numberB.intValue = randomAInt()
    resultLabel.stringValue = ""
}

func randomAInt() -> Int32 {
	return Int32(arc4random_uniform(10))
}
  • 最后别忘了在 windowDidLoad() 中随机生成两个数。修改 windowDidLoad() 方法:
override func windowDidLoad() {
    super.windowDidLoad()
    reset()
}

大功告成!运行一下你的程序,使用 Command+Shift+5 快速调出模拟的 TouchBar 窗口测试吧!


References - 参考