本文翻譯自 raywenderlich.com 的 How to Use NSTouchBar on macOS,已咨詢對(duì)方網(wǎng)站,可至多翻譯 10 篇文章。
各位若有英語(yǔ)閱讀能力的話,還是先打賞然后去閱讀英文原吧??。
綜上,此翻譯版本僅供參考,謝絕轉(zhuǎn)載。也歡迎你點(diǎn)擊我的頭像查看我翻譯的其他 macOS 開發(fā)教程??

對(duì)了我跟著這個(gè)教程敲代碼的時(shí)候發(fā)現(xiàn)文中所有的
@available(OSX 10.12.1, *)其實(shí)應(yīng)為@available(OSX 10.12.2, *),但是出于對(duì)原文的尊重沒(méi)有修改,請(qǐng)各位注意~
等了好久好久終于等到今天之后,Apple 終于發(fā)布了新款的 MacBook Pro,它最惹人矚目的應(yīng)該就是那塊(小小的)觸屏了吧。
新款設(shè)備用全新的 Touch Bar 替代了原有的功能鍵,它們可擴(kuò)展、支持多點(diǎn)觸控,更重要的是,Touch Bar 對(duì)開發(fā)者們完全開放,這意味著你的 macOS app 可以獲得一種全新的交互方式。
如果你是一個(gè) macOS 開發(fā)者,你一定很希望自己的 app 能夠立刻使用上這項(xiàng)前沿科技。在這個(gè)教程中,我會(huì)將向你們展示如何使用全新的 NSTouchBar API 來(lái)為你的 macOS app 創(chuàng)建一個(gè)動(dòng)態(tài)的 Touch Bar。
注意:這個(gè)教程需要 Xcode 8.1 或更高版本以及 macOS 10.12.1 (Build 16B2657) 或更高版本,否則的話你將無(wú)法運(yùn)行 Touch Bar 模擬器。你可以在 ? → 關(guān)于本機(jī)里點(diǎn)擊數(shù)字版本號(hào)來(lái)查看 build 版本。
如果你的版本不夠,你可以在 Apple 的網(wǎng)站上 下載。
Touch Bar 是個(gè)啥?
如上文所述,Touch Bar 是一塊安裝在鍵盤上方的細(xì)長(zhǎng)形的觸摸屏,它允許用戶使用一種全新的方式來(lái)與 app(以及 Mac)進(jìn)行交互。

在 Touch Bar 上有三個(gè)默認(rèn)的部分:
- 系統(tǒng)按鈕:根據(jù)運(yùn)行的 app,這里將會(huì)顯示一個(gè)系統(tǒng)級(jí)的按鈕,比如 esc;
- App 區(qū)域:你的 app 可以控制的顯示區(qū)域,也就是我們的主舞臺(tái);
- 控制條:這里用于顯示你熟悉的控制按鈕,比如亮度、音量等。
和其它許多 Apple 的新科技一樣,Touch Bar 也擁有自己的《人機(jī)交互則例(Human Interface Guidelines)》,為了你的 app 和其它 Mac app 擁有統(tǒng)一的用戶體驗(yàn),你應(yīng)當(dāng)遵循這份則例。你可以點(diǎn)擊這里閱讀它。
概括說(shuō)來(lái),這份則例中比較重要的幾點(diǎn)有:
- App 中的某個(gè)功能不應(yīng)該只能在 Touch Bar 中使用:即使部份用戶還未升級(jí)到最新的硬件,你的 app 也應(yīng)該盡量為他們提供一樣的功能。如果你決定為 Touch Bar 加入某些功能,請(qǐng)確保這個(gè)功能也能在 app 的其他某處也可以訪問(wèn)到。另外 Touch Bar 是可以被用戶禁用的,所以也別太指望你的用戶能一直看到它;
- Touch Bar 是鍵盤的延伸,而不是一個(gè)顯示屏:誠(chéng)然,Touch Bar 是一個(gè)顯示屏,但是它不是顯示器的延伸,而是一個(gè)與 app 交互的窗口。你不應(yīng)該在 Touch Bar 上用滾來(lái)滾去的內(nèi)容或 blingbling 的警告打擾用戶的視線;
- 快速響應(yīng):用戶在鍵盤上按下一個(gè)真實(shí)的按鍵時(shí),按鍵會(huì)立即給予一個(gè)反饋(也就是被按下去了)。同理,用戶在 Touch Bar 上按下一個(gè)虛擬按鈕時(shí),你也應(yīng)該給他們提供即時(shí)的反饋。
如何讓你的 app 支持 Touch Bar
要讓你的 app 支持 Touch Bar,你需要使用 Apple 提供的兩個(gè)類:NSTouchBar 和 NSTouchBarItem(當(dāng)然還有他們的子類)。
某些 NSTouchBarItem 的子類提供了這些功能:
- Slider:滑動(dòng)調(diào)節(jié)某個(gè)值;
- Popover:把更多功能藏入一個(gè)二級(jí)菜單中;
- Color Picker:和名字一樣,用來(lái)選取顏色的咯???♀?;
- Custom:這個(gè)子類是你的天下,你可以在它的里面塞入文本、按鈕以及其他各種各樣的控件。
從文字大小顏色到圖片內(nèi)容,你可以隨意自定義你的 item,從而為你的用戶提供一個(gè)傳統(tǒng)鍵盤無(wú)法提供的、更加牛×的交互方式,但是請(qǐng)時(shí)刻請(qǐng)謹(jǐn)記《人機(jī)交互則例》?,F(xiàn)在我們要開始動(dòng)工啦。
準(zhǔn)備開始
在開始敲代碼之前,請(qǐng)先點(diǎn)擊這里下載初始項(xiàng)目的源代碼。
我們要編寫的 app 是一個(gè)簡(jiǎn)單的旅行記錄 app。打開初始項(xiàng)目,如果你的設(shè)備不支持 Touch Bar,請(qǐng)點(diǎn)擊 Xcode 菜單欄上的 Window → Show Touch Bar,Touch Bar 的模擬器就會(huì)出現(xiàn)在屏幕上。

編譯并運(yùn)行你的 app,你將會(huì)看到 Touch Bar 上除了 esc 按鈕和控制條以外空空如也。


我們要做的第一步是告訴系統(tǒng)我們的 app 需要自定義 Touch Bar。打開 AppDelegate.swift,將這些代碼添加到 applicationDidFinishLaunching(_:) 方法中:
func applicationDidFinishLaunching(_ aNotification: Notification) {
if #available(OSX 10.12.1, *) {
NSApplication.shared().isAutomaticCustomizeTouchBarMenuItemEnabled = true
}
}
這些代碼將會(huì)幫你搞定啟用 Touch Bar 所需要的各種操作,(在寫這篇文章的時(shí)候)Xcode 還沒(méi)有 macOS 10.12.1 的配套 SDK,所以你需要在 Touch Bar 相關(guān)的代碼周邊添加 #available(OS X 10.12.1, *),當(dāng)然如果你忘了這件事,Xcode 會(huì)給你一個(gè)溫馨的提醒??。
打開 WindowController.swift,找到 makeTouchBar(),這個(gè)方法用于檢測(cè) ViewController 是否含有一個(gè)可以被返回的 Touch Bar,如果有,它會(huì)把這個(gè) Touch Bar 返回給 Window,然后呈現(xiàn)給用戶?,F(xiàn)在,我們還沒(méi)有創(chuàng)建 Touch Bar,所以什么也不會(huì)發(fā)生。
在你開始創(chuàng)建自己的 Touch Bar 和 Touch Bar Item 之前,你需要注意這些類都需要獨(dú)一無(wú)二的 identifier(標(biāo)識(shí)符),打開 TouchBarIdentifiers.swift,你將能看到兩個(gè)擴(kuò)展定義了一些標(biāo)識(shí)符:NSTouchBarCustomizationIdentifier 和 NSTouchBarItemIdentifier。
前往 ViewController.swift,并把這些帶碼添加到文件最后 // MARK: - TouchBar Delegate 注釋的后方:
@available(OSX 10.12.1, *)
extension ViewController: NSTouchBarDelegate {
override func makeTouchBar() -> NSTouchBar? {
// 1
let touchBar = NSTouchBar()
touchBar.delegate = self
// 2
touchBar.customizationIdentifier = .travelBar
// 3
touchBar.defaultItemIdentifiers = [.infoLabelItem]
// 4
touchBar.customizationAllowedItemIdentifiers = [.infoLabelItem]
return touchBar
}
}
在此處,通過(guò)重寫 makeTouchBar() 方法,你給你的 View 或 Window 創(chuàng)建了一個(gè) Touch Bar,在這個(gè)方法中:
- 創(chuàng)建一個(gè)新的
TouchBar并設(shè)置它的 delegate; - 設(shè)置 customizationIdentifier(自定義標(biāo)識(shí)符),每個(gè)
TouchBar和TouchBarItem都需要一個(gè)獨(dú)一無(wú)二的標(biāo)識(shí)符; - 設(shè)置 Touch Bar 的默認(rèn) item 標(biāo)識(shí)符,這將會(huì)告訴 Touch Bar 它將會(huì)容納哪些 item;
- 這一步是允許用戶可以自定義的 item。作為參考,你可以打開 Finder,點(diǎn)擊菜單欄上的 顯示 → 自定 Multi-Touch Bar看看自定義 Touch Bar 是什么效果。
接下來(lái),我們還需要設(shè)置 .infoLabelItem 是什么樣子的,在同一個(gè) extension 中添加:
func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem? {
switch identifier {
case NSTouchBarItemIdentifier.infoLabelItem:
let customViewItem = NSCustomTouchBarItem(identifier: identifier)
customViewItem.view = NSTextField(labelWithString: "\u{1F30E} \u{1F4D3}")
return customViewItem
default:
return nil
}
}
通過(guò)實(shí)現(xiàn)touchBar(_:makeItemForIdentifier:) 方法,你可以自定義你的 Touch Bar Item,在段代碼里,你創(chuàng)建了一個(gè) NSCustomTouchBarItem,并把它的 view 設(shè)置為了 NSTextField。
編譯并運(yùn)行你的 app,你可以看到 Touch Bar 多了一個(gè) item。

耶??!通過(guò)一番努力你得到了……兩個(gè) emoji…好吧,現(xiàn)在我們來(lái)添加一些別的控件。

Text Field 和 Scrubber
在 makeTouchBar() 里,把 touchBar.defaultItemIdentifiers = [.infoLabelItem] 修改為:
touchBar.defaultItemIdentifiers = [.infoLabelItem, .flexibleSpace, .ratingLabel, .ratingScrubber]
這些代碼會(huì)讓 Touch Bar 顯示三個(gè) item:一個(gè) label、一個(gè) scrubber 以及一個(gè) .flexibleSpace。這是一個(gè)動(dòng)態(tài)的間距,它可以把各種 item 按組進(jìn)行整潔的分類。此外你還可以使用 .fixedSpaceSmall 和 .fixedSpaceLarge 這兩個(gè)固定間距。
和之前的那個(gè) label 一樣,你也需要自定義這些 item,把這些 cases 添加到 touchBar(_:makeItemForIdentifier:) 里的 switch:
case NSTouchBarItemIdentifier.ratingLabel:
// 1
let customViewItem = NSCustomTouchBarItem(identifier: identifier)
customViewItem.view = NSTextField(labelWithString: "Rating")
return customViewItem
case NSTouchBarItemIdentifier.ratingScrubber:
// 2
let scrubberItem = NSCustomTouchBarItem(identifier: identifier)
let scrubber = NSScrubber()
scrubber.scrubberLayout = NSScrubberFlowLayout()
scrubber.register(NSScrubberTextItemView.self, forItemIdentifier: "RatingScrubberItemIdentifier")
scrubber.mode = .fixed
scrubber.selectionBackgroundStyle = .roundedBackground
scrubber.delegate = self
scrubber.dataSource = self
scrubberItem.view = scrubber
scrubber.bind("selectedIndex", to: self, withKeyPath: #keyPath(rating), options: nil)
return scrubberItem
一步一步來(lái)看:
- 為「評(píng)分」新建了一個(gè)新的 label item;
- 然后創(chuàng)建一個(gè)新的 item 用來(lái)展示
NSScrubber。這是一個(gè) Touch Bar 特有的控件,它允許你在若干個(gè)項(xiàng)目中進(jìn)行選擇。Scrubber 需要一個(gè)代理來(lái)處理事件,你需要做的是設(shè)置一個(gè)delegate(初始項(xiàng)目的源代碼已經(jīng)在 ViewController 里實(shí)現(xiàn)過(guò)這個(gè)協(xié)議了)。
編譯并運(yùn)行你的 app,你將會(huì)看到 Touch Bar 里多出了兩個(gè)新的 item,當(dāng)你點(diǎn)擊某個(gè) scrubber 里的項(xiàng)目后,在 app 的主窗口里能看到數(shù)值的調(diào)整。

Segmented Control
接下來(lái),我們來(lái)添加一個(gè) Segmented Control。這個(gè)控件沒(méi)有使用 Delegate 模式,因此這剛好可以讓你體驗(yàn)一下怎么設(shè)置 Touch Bar 里的 Target-Action(目標(biāo)動(dòng)作)?;氐?makeTouchBar() 中,把這三個(gè) item 添加到 defaultItemIdentifiers 里:
touchBar.defaultItemIdentifiers = [.infoLabelItem, .flexibleSpace, .ratingLabel, .ratingScrubber, .flexibleSpace, .visitedLabelItem, .visitedItem, .visitSegmentedItem]
以及把最后的三個(gè) case 添加到 touchBar(_:makeItemForIdentifier:) 中:
case NSTouchBarItemIdentifier.visitedLabelItem:
// 1
let customViewItem = NSCustomTouchBarItem(identifier: identifier)
customViewItem.view = NSTextField(labelWithString: "Times Visited")
return customViewItem
case NSTouchBarItemIdentifier.visitedItem:
// 2
let customViewItem = NSCustomTouchBarItem(identifier: identifier)
customViewItem.view = NSTextField(labelWithString: "--")
customViewItem.view.bind("value", to: self, withKeyPath: #keyPath(visited), options: nil)
return customViewItem
case NSTouchBarItemIdentifier.visitSegmentedItem:
// 3
let customActionItem = NSCustomTouchBarItem(identifier: identifier)
let segmentedControl = NSSegmentedControl(images: [NSImage(named: NSImageNameRemoveTemplate)!, NSImage(named: NSImageNameAddTemplate)!], trackingMode: .momentary, target: self, action: #selector(changevisitedAmount(_:)))
segmentedControl.setWidth(40, forSegment: 0)
segmentedControl.setWidth(40, forSegment: 1)
customActionItem.view = segmentedControl
return customActionItem
一步一步看:
- 和之前一樣,創(chuàng)建一個(gè)簡(jiǎn)單的 Label;
- 創(chuàng)建另一個(gè) Label,但這一次使用 bind 來(lái)把 Label 要顯示的文本綁定到一個(gè)屬性上;
- 最后,創(chuàng)建一個(gè) Segmented Control,并設(shè)置它的 action。你可以看到,為它設(shè)置一個(gè) action 和其他常見控件是完全一樣的。
編譯并運(yùn)行,除了 Scrubber,你現(xiàn)在還可以和 Segmented Control 交互了。此外,在 Touch Bar 里修改一個(gè)數(shù)值,app 的主窗口中也會(huì)顯示出來(lái),反之亦然。

彩色按鈕
能讓用戶使用 Touch Bar 來(lái)進(jìn)行「保存」操作是一個(gè)不錯(cuò)的點(diǎn)子,因?yàn)檫@個(gè)按鈕和別的按鈕都不一樣,我們可以把它設(shè)置成綠色的。你可以使用 NSButton 的 bezelColor 屬性來(lái)給它設(shè)置一個(gè)特殊的顏色。
打開 TouchBarIdentifiers.swift,在 NSTouchBarItemIdentifier extension 里,添加這些代碼:
static let saveItem = NSTouchBarItemIdentifier("com.razeware.SaveItem")
這將會(huì)從頭開始創(chuàng)建一個(gè)標(biāo)識(shí)符,以此允許你在 Touch Bar 里添加一個(gè) item。
返回 ViewController.swift,添加一個(gè)新的 .flexSpace 和 .saveItem 到 Touch Bar 的 defaultItemIdentifiers 里:
touchBar.defaultItemIdentifiers = [.infoLabelItem, .flexibleSpace, .ratingLabel, .ratingScrubber, .flexibleSpace, .visitedLabelItem, .visitedItem, .visitSegmentedItem, .flexibleSpace, .saveItem]
基本上完成了,最后一步是對(duì)按鈕進(jìn)行一些設(shè)置。在 touchBar(_:makeItemForIdentifier:) 中添加最后一個(gè) case:
case NSTouchBarItemIdentifier.saveItem:
let saveItem = NSCustomTouchBarItem(identifier: identifier)
let button = NSButton(title: "Save", target: self, action: #selector(save(_:)))
button.bezelColor = NSColor(red:0.35, green:0.61, blue:0.35, alpha:1.00)
saveItem.view = button
return saveItem
通過(guò) bezelColor,你已經(jīng)把這個(gè)按鈕成功地修改成了綠色。
編譯并運(yùn)行,你會(huì)看到 Touch Bar 上有了一個(gè)綠色的按鈕,它和窗口中的 Save 按鈕擁有一樣的功能。

現(xiàn)在該做些啥?
你可以點(diǎn)擊這里下載最終完成的項(xiàng)目。
這些就是 Touch Bar 的基礎(chǔ)了。顯然,Apple 希望 Touch Bar 的開發(fā)過(guò)程越簡(jiǎn)單越好,因此你可以盡快把這些特性盡快地帶給新 MacBook Pro 的用戶。
在這個(gè)教程中,你學(xué)到了:
- 如何設(shè)置你的 app,讓它能顯示 Touch Bar;
- 如何在 Touch Bar 里顯示靜止的 Label;
- 如何使用 binding(綁定)來(lái)添加一個(gè)動(dòng)態(tài)的 Label;
- 如何在 Touch Bar 中添加控件,并處理它們的事件。
請(qǐng)不要在此停下!NSTouchBar 和 NSTouchBarItem 中還有很多值得探索的特性。你可以試著在 Touch Bar 里添加一個(gè) Popover,或者也可以試試在你的 app 里格式化一個(gè)文本,你還可以在試著去在 Interface Builder 里創(chuàng)建一個(gè) Touch Bar。
