Touch on Mac 文件:遷移指南、遊戲技術說明與 HIG

議程之後,Apple 為 Touch on Mac 公布了什麼? 到了 10 月 28 日晚上,中午還回傳 404 的 AppKit 文章「Adopting gesture recognizers in your app」已經上線,附上一張四列的表格,為每一種處理滑鼠的程式碼指出下一步;第二篇文章「Handling AppKit gestures」說明了排他視圖、針對卡住之辨識器的兩秒逾時,以及兩個除錯用的預設值;一份新的技術說明 TN3216 涵蓋遊戲與模擬滑鼠事件的順序;三個 Human Interface Guidelines 頁面則帶有日期為 10 月 28 日、關於 Mac 觸控的變更記錄。1234 登陸頁面的「Documentation resources」區塊現在連結到這篇文章與 file promises 範例,「Design resources」區塊連結到那三個 HIG 頁面,而「Xcode 27.3 beta」區塊仍標示「Coming soon」。5

TL;DR: 開發者那篇文章於 10 月 28 日下午 1 點 47 分(PT)發布,依據的是三場 Tech Talks 與一份 6 月的技術說明;它需要的那篇 AppKit 文章在中午回傳 404。到了晚上 9 點 51 分,那篇文章已經上線,旁邊還多了兩份文件。這篇遷移文章把您的程式碼分成四列:標準控制項與辨識器什麼都不用做;mouseDown(with:) 覆寫與追蹤迴圈會得到單指模擬,應改寫為辨識器;覆寫滑鼠方法的控制項子類別會退回追蹤迴圈;永遠不結束的自訂辨識器會一直占住它的排他視圖。1 第三列背後的規則是,AppKit 依據方法是否被覆寫來判斷,「not on what the override does」(而不是看覆寫做了什麼),所以呼叫 super 也沒有用。1 在保留的迴圈旁加上一個 buttonMask 為 0、僅限觸控的 NSPanGestureRecognizer,這個做法現在有了文件,連同不設定遮罩的確切後果:滑鼠拖曳會啟動辨識器,而您的 mouseDown(with:) 永遠不會執行。1 缺少 Info.plist 鍵時,排他視圖預設為開啟;Xcode 的 macOS App 範本只在 app 生命週期為 AppKit app delegate 的專案中把這個鍵寫成 NO,預設的 SwiftUI 生命週期則不會。這是我在這台 Mac 上讀 Xcode 27.1 與 27.2 beta 內的範本得知的。27 卡住的辨識器會在互動結束兩秒後被取消;除錯期間,一個使用者預設值會在缺少覆寫時讓 app 當掉,另一個則以當掉取代無聲的取消。2 遊戲技術說明列出點一下與拖曳所產生的確切滑鼠事件,要求引擎不要累加位移量,並說明在設定 NSIsTouchNative 之前,每個觸控表面都會以一個額外的模擬 GCMouse 出現。3 HIG 把觸控稱為「a secondary input mode」(次要的輸入模式),要求讓「tap targets bigger, not the controls themselves」(點按目標變大,而不是控制項本身變大),並為過去只在懸停時出現的控制項新增六條規則。4 仍然沒有到的:Xcode 27.3 beta,以及三個系統手勢開關,它們在晚上 9 點 51 分(PT)仍回傳 404。56

上線了什麼,又是何時上線

以下每一項都附上我檢查的時間,因為伺服器自己的時間戳與頁面在當天呈現的內容並不一致;當天的即時貼文保留了一整天的紀錄,並為這批文件加了一列較晚的紀錄。

  • AppKit 文章。 「Adopting gesture recognizers in your app」在中午(PT)從 Apple 的文件端點回傳 404;到了晚上 9 點 51 分已經上線,伺服器給的修改時間戳是下午 2 點 30 分(PT),它列在 AppKit 的 Essentials 一節、排在「Adopting Liquid Glass」之後,也列在框架 Gestures 頁面的「Gesture handling」之下,而 Wayback Machine 對它的第一次擷取是下午 6 點 08 分(PT)(憑據見資料來源)。1 「Bring Touch to your Mac app」的 Tech Talk 頁面仍然沒有連結到它。5
  • 第二篇文章。 「Handling AppKit gestures」就在它旁邊,同樣位於 Gestures 頁面。它沒有修訂記錄,6 月的技術說明沒有連結到它,Wayback Machine 沒有它的擷取,伺服器時間戳是 10 月 27 日晚上 10 點 35 分(PT);它不在我中午檢查的頁面之列,所以我無法說出它是何時上線的。2
  • 一份新的技術說明。 「TN3216: Supporting touch input for games on macOS」的修訂記錄只有一行「2026-10-28 First published」,伺服器時間戳是下午 2 點 09 分(PT)。3
  • 三個 HIG 頁面。 「Designing for macOS」、「Gestures」與「Pointing devices」的結尾現在都有日期為 2026 年 10 月 28 日的變更記錄。我在中午抓取的版本完全沒有新文字;晚上 9 點 51 分抓取的版本則有。新「Gestures」頁面的伺服器修改標頭寫的是 10 月 27 日 5:11 UTC,比缺少這些文字的中午版本還早;這兩件事我都如實呈現,但不給出任何發布時刻。4
  • 登陸頁面。 上午 11 點 49 分時,「Tools and documentation」之下的三個區塊都標示「Coming soon」,連結保留在 HTML 註解中。晚上 9 點 51 分時,「Documentation resources」區塊(「Learn how to build and optimize your app for touch input.」)連結到這篇文章與 file promises 範例,「Design resources」區塊(「Create experiences that take advantage of touch user interface patterns.」)則連結到那三個 HIG 頁面與 Apple Design Resources,後者的產品外框清單中包含「MacBook Pro PROLED XDR Display」。「Xcode 27.3 beta」區塊仍標示「Coming soon」,而 HTML 註解 TK skill 依然留在頁面中。5
  • 範例。 「Supporting drag and drop through file promises」的頁面現在提到它的辨識器:「The recognizers work the same way with a pointer or with direct touches on a Mac with a touch display.」(不論使用指標,或在觸控螢幕 Mac 上直接觸控,這些辨識器的運作方式都相同。)6
  • 其他都沒有變動。 開發者新聞 feed 最新的一則仍是上午 10:00:55(PDT)的「Say hello to Touch on Mac」;releases feed 最新的項目是 10 月 26 日的小版本更新,沒有 Xcode 27.3 或 macOS 27.3 beta;Tech Talks 列表還是那三場議程;ActivityKit 頁面帶著 10 月 8 日的時間戳,仍然沒有列出 macOS 平台。56

下午那篇文章說,成文的規則就是議程與 6 月的技術說明。本文談的是成文規則新增了什麼,並在晚間檢查發現不同資訊之處更正下午那篇文章;更正清單放在文末。

為您的程式碼分流的那張表

文章「Prepare your app for a Mac with a touch display」一節是一張三欄四列的表格,三欄分別是輸入處理、觸控下的行為,以及下一步。Apple 的行為欄照錄原文;另外兩欄經過濃縮,只有第三列的下一步是 Apple 的原文:1

您的程式碼 Apple 的「Behavior with touch」 下一步
標準 AppKit 控制項與手勢辨識器 「Works with touch, with no changes needed.」 測試。
mouseDown(with:) 覆寫與追蹤迴圈 「AppKit emulates a single-finger tap or drag, but supports multi-finger gestures and individual touches only through gesture recognizers.」 改用辨識器取代。
覆寫了滑鼠處理方法的 NSButton、NSSlider 與其他 AppKit 視圖的子類別 「AppKit falls back to a tracking loop.」 「Replace the overrides with target-action pairs for tracking events.」
自訂的 NSGestureRecognizer 子類別 「A recognizer that never reaches a terminal state keeps its exclusive view, which can make your app appear unresponsive.」 處理取消,並抵達終止狀態。

表格之後的測試有四行,值得照原樣執行:「Controls respond to a tap, and buttons highlight and activate as expected」、「Dragging a view, selecting text, and scrolling work with one finger」、「Pinch and rotate work where your app supports them」,以及「Small targets, such as resize grips and grab handles, are easy to hit with a finger」(控制項回應點一下,按鈕如預期反白並啟動;以單指拖曳視圖、選取文字與捲動都能運作;app 支援捏合與旋轉之處都能運作;調整大小的握把與抓取控點等小目標用手指也容易點中)。1 搜尋清單把議程對 mouseDown(with:) 覆寫的稽核擴大到五個符號:「search your project for nextEvent(matching:until:inMode:dequeue:) and trackEvents(matching:timeout:mode:handler:), because tracking loops typically call these methods. Also search for mouseDown(with:) and mouseDragged(with:) overrides, and for uses of currentEvent in control action methods.」(在專案中搜尋追蹤迴圈通常會呼叫的這兩個方法,也搜尋 mouseDown(with:) 與 mouseDragged(with:) 覆寫,以及控制項動作方法中對 currentEvent 的使用。)1 最後一項檢查正是第四列談的事:「confirm that your app stays responsive after a touch interaction ends, and that every gesture recognizer action method handles the NSGestureRecognizer.State.cancelled state.」(確認觸控互動結束後 app 仍有回應,而且每個手勢辨識器的動作方法都處理了 cancelled 狀態。)1

Overview 中的兩句話,解決了議程留給讀者推論的一個問題。模擬是單指的:「AppKit emulates mouse events from a single finger tapping or dragging」,以及「Relying on this emulation, however, means you miss out on multi-finger gestures, like pinch and rotate, that gesture recognizers handle.」(不過,依賴這種模擬就會錯過手勢辨識器所處理的捏合、旋轉等多指手勢。)1 測試設備則是議程點名的那一個,現在寫進了一則提示:「Use Sidecar to test touch input without a Mac that has a touch display」,因為「AppKit then routes touch on its screen the same way it does on a Mac with a touch display」(AppKit 會以與觸控螢幕 Mac 相同的方式,處理 iPad 螢幕上的觸控)。1

覆寫規則

表格的第三列背後有一條規則,議程與 6 月的技術說明都沒有說得這麼直白。如果您繼承 NSButton 並覆寫 mouseDown(with:) 來加上反白動畫,「AppKit treats your override as a replacement for its own click handling, rather than as a side effect」(AppKit 會把您的覆寫視為取代它自己的點按處理,而不是附帶效果),按鈕便退回追蹤迴圈。1 接著是:「AppKit decides based on whether your subclass overrides the method, not on what the override does」(AppKit 依據子類別是否覆寫了這個方法來判斷,而不是看覆寫做了什麼),所以即使覆寫呼叫了 super.mouseDown(with:),仍然會退回;而且「Overriding mouseDragged(with:) or mouseUp(with:) has the same effect.」(覆寫 mouseDragged(with:) 或 mouseUp(with:) 也有相同效果。)1

替代做法是議程中的控制項事件模式,而文章的程式碼清單與議程的版本有一處細節不同,值得照抄。議程 2:24 的清單為 [.trackingExited, .trackingEndedInside] 註冊一個動作,為 .trackingCancelled 另外註冊一個;文章的隱藏集合則是 [.trackingExited, .trackingEndedInside, .trackingEndedOutside, .trackingCancelled],多涵蓋了在按鈕外結束的按壓。19 trackingEndedOutside 並不新:Apple 的文件把它列為 macOS 11.0,而 macOS 27.0 SDK 在 NSControl.h 中把它宣告為 NSControlEventTrackingEndedOutside = 1 << 7(作者分析)。67 文章也提到,動作方法必須以 @objc 公開給 Objective-C;如果您只需要啟動的那一刻,primaryActionTriggered 會觸發,「regardless of whether someone used a mouse, trackpad, or touch to produce it」(不論使用者是用滑鼠、觸控式軌跡板還是觸控產生的)。1

容器視圖也有同樣的陷阱:「If you start a drag from mouseDragged(with:) in an NSTableView, NSCollectionView, NSOutlineView, or NSBrowser, AppKit falls back to a tracking loop for the interaction」(在這些容器視圖中從 mouseDragged(with:) 開始拖曳,AppKit 就會讓這次互動退回追蹤迴圈),解法是資料來源與代理的拖曳方法,以表格為例就是 tableView(_:pasteboardWriterForRow:)。1

對於兩種常見的 mouseDown(with:) 覆寫,文章給出修改前後的對照。「Most mouseDown(with:) overrides do one of two things: mark an item as selected when someone clicks it, or show a menu when someone Control-clicks.」(大多數 mouseDown(with:) 覆寫只做兩件事之一:在點按時把項目標為已選取,或在 Control 點按時顯示選單。)1 選取的部分,改為觀察集合視圖項目或表格列視圖的 isSelected,但要注意這個觀察者「also runs when your code changes the selection programmatically」(在您的程式碼以程式方式變更選取時也會執行);選單的部分,覆寫 menu(for:) 讓 AppKit 呈現,或在選單與點按無關時設定 menu 或 defaultMenu。1

在保留的迴圈旁加上僅限觸控的辨識器

議程推薦,在既有的追蹤迴圈旁加上一個僅限觸控的平移辨識器,作為小版本更新的途徑。文章在「Add touch support to your custom tracking loop」之下記載了同一套做法,包括共用的開始、繼續、結束與取消函式,接著以議程沒有篇幅交代的精確度,說明每個屬性的作用:1

let touchRecognizer = NSPanGestureRecognizer(target: self, action: #selector(handleTouchPan))
touchRecognizer.buttonMask = 0
touchRecognizer.allowedTouchTypes = .direct
addGestureRecognizer(touchRecognizer)

「setting buttonMask to 0 means the gesture recognizer only recognizes touches, not mouse or trackpad input, so your tracking loop keeps handling the mouse and trackpad exactly as before.」(把 buttonMask 設為 0,代表手勢辨識器只辨識觸控,不辨識滑鼠或觸控式軌跡板的輸入,因此您的追蹤迴圈會跟以前完全一樣地處理滑鼠與觸控式軌跡板。)1 若遮罩維持預設值,辨識器「also responds to the primary mouse button. A mouse drag then begins the gesture recognizer, and AppKit doesn’t call your mouseDown(with:) override, so the tracking loop doesn’t run. A click that doesn’t move still reaches your tracking loop.」(也會回應滑鼠主按鈕;滑鼠拖曳會啟動手勢辨識器,AppKit 不會呼叫您的 mouseDown(with:) 覆寫,追蹤迴圈因此不會執行;沒有移動的點按仍會送到追蹤迴圈。)1 所以這個遮罩不是可有可無的細節;少了它,迴圈會在無聲無息中收不到拖曳。allowedTouchTypes 那一行則是雙重保險:「Direct touches are already the default for allowedTouchTypes, and setting it to .direct states that intent explicitly.」(直接觸控本來就是 allowedTouchTypes 的預設值,設為 .direct 只是把意圖明確寫出來。)1 Apple 自己對這個做法的評價是:「This is often the fastest, lowest-risk way to add touch support to code with a tracking loop you’d rather not rewrite all at once.」(對於不想一次全部重寫的追蹤迴圈程式碼,這往往是加入觸控支援最快、風險最低的方式。)1

如果您改為取代整個迴圈,文章的平移範例會在 .cancelled 時把標題還原到起始位置,而原本會啟動拖曳工作階段的迴圈,則改把辨識器而非事件傳給 beginDraggingSession(items:gesture:source:)。1

以兩個代理方法篩選

相對於議程,最新的內容是「Filter gesture recognizer candidates」這一節。辨識器是透過點擊測試(hit-testing)收集的,而不是透過回應者鏈:「Gesture recognizers don’t use the responder chain.」1 AppKit 收集辨識器時,「not every recognizer it finds is actually applicable to the interaction」(找到的辨識器並不都真的適用於這次互動),app 透過兩個代理方法移除不適用的辨識器:gestureRecognizer(_:shouldAttemptToRecognizeWith:),用來「to inspect the initiating NSEvent for pointer-based input」(檢查指標輸入中起始的 NSEvent),以及 gestureRecognizer(_:shouldReceive:),用來「to inspect each new NSTouch for touch input」(檢查觸控輸入中每一個新的 NSTouch)。1 兩者的用意是:「Either method gives you the initial input, something the recognizer doesn’t otherwise expose, and lets you stop the recognizer from handling that event stream or touch.」(兩個方法都會給您起始的輸入,這是辨識器本身不會公開的,並讓您阻止辨識器處理那一串事件或那一次觸控。)1 分工也很嚴格:「Direct touches don’t call this method. To filter them, implement gestureRecognizer(:shouldReceive:) instead, as shown above.」(直接觸控不會呼叫這個方法;要篩選它們,請改為實作 gestureRecognizer(:shouldReceive:),如上所示。)1 只透過事件方法篩選,直接觸控就不會經過那個代理的篩選。

兩個範例是我預期會被大量照抄的模式。調整大小的握把:把調整大小的辨識器掛在整個視圖上,並在兩個代理方法中回傳位置是否落在握把的框架內;回傳 true「only to give the gesture recognizer a chance to attempt recognition, not to guarantee it succeeds」(只是給手勢辨識器一個嘗試辨識的機會,並不保證成功)。1 片段時間軸:在時間軸上放一個辨識器,在事件方法中對片段做點擊測試,把它存起來供動作方法使用,若沒有點中任何片段就回傳 false。1 對小目標的配套警告是:「If a custom control expects a touch to land inside its visible area, update it to handle touches that land outside that area.」(如果自訂控制項預期觸控會落在它的可見範圍內,請修改它,讓它也能處理落在範圍外的觸控。)1

留下來的辨識器之間如何排定優先順序,靠的是失敗條件;文章重複了第二篇文章的警告:「Use this sparingly, because the waiting recognizer responds more slowly」(請謹慎使用,因為等待中的辨識器回應會變慢),並舉例說「making a single click wait for a double click to fail makes clicks feel sluggish」(讓單擊等待雙擊失敗,會讓點按感覺遲鈍)。1

currentEvent、懸停與滑動

「Handle touch input」以文件的形式重述了議程中統攝一切的那句話:AppKit「doesn’t deliver direct touches (NSTouch) through the responder chain or through event monitors」(不會透過回應者鏈或事件監視器傳遞直接觸控),而且「Gesture recognizers are the only way to respond to multi-finger gestures or to receive individual touches from a Mac with a touch display or a Sidecar-connected iPad.」(要回應多指手勢,或接收來自觸控螢幕 Mac 或透過 Sidecar 連接之 iPad 的個別觸控,手勢辨識器是唯一的途徑。)1 AppKit 的輸入總覽頁面從回應者的角度說了同一件事:「The responder chain receives touch events only from indirect touches on a trackpad, if a view asks for them. It doesn’t receive touch events from direct touches on a Mac with a touch display.」(回應者鏈只在視圖要求時,接收來自觸控式軌跡板的間接觸控事件;它不接收觸控螢幕 Mac 上的直接觸控事件。)6

關於能力檢查,建議是盡量避免:「In most cases, instead of checking whether a device supports touch, write your event-handling code to work the same way regardless of input source, and decide on alternate presentations based on the interaction type.」(在大多數情況下,與其檢查裝置是否支援觸控,不如讓事件處理程式碼不論輸入來源都以相同方式運作,再依互動類型決定是否採用替代的呈現方式。)1 文中為 touchCapabilities 舉的唯一例子是「capturing a signature on a touch display」(在觸控螢幕上擷取簽名)。1

currentEvent 有自己的一節。因為控制項的互動改由辨識器處理,「currentEvent no longer reliably returns the event that initiated an action」(currentEvent 不再能可靠地回傳觸發動作的事件),修改前後的對照把事件的修飾鍵旗標換成類別層級的 NSEvent.modifierFlags,並附上但書:「These report the state at the time you query them, not at the time of the original event. A touch doesn’t register in pressedMouseButtons.」(這些回報的是查詢當下的狀態,而不是原始事件發生時的狀態;觸控不會登記在 pressedMouseButtons 中。)1 不變的部分是:「Keyboard events and scroll-wheel events behave the same on every input source: AppKit delivers them through the responder chain. Hover tracking, which relies on mouseMoved(with:) and NSTrackingArea, applies to pointer input only, because a touch display has no hover.」(鍵盤與滾輪事件在每種輸入來源下的行為都相同,AppKit 透過回應者鏈傳遞它們;依賴 mouseMoved(with:) 與 NSTrackingArea 的懸停追蹤只適用於指標輸入,因為觸控螢幕沒有懸停。)1

滑動不會被轉換:「AppKit doesn’t translate touch swipes for you.」1 如果您使用 trackSwipeEvent(options:dampenAmountThresholdMin:max:usingHandler:),就要自行實作;至於上一頁與下一頁的導覽,先檢查 NSEvent.isTouchSwipeNavigationEnabled,「because the person can turn off swipe navigation in System Settings」(因為使用者可以在「系統設定」中關閉滑動導覽)。1 這個屬性是新的:Apple 的文件把它列為 macOS 27.0,而 macOS 27.0 SDK 的 NSEvent.h 在第 496 行把它宣告為 API_AVAILABLE(macos(27))(作者分析)。67

自訂辨識器:契約、逾時與兩個預設值

文章為自訂 NSGestureRecognizer 子類別列出的準則,是一份七點的契約:在 mouseCancelled(with:) 與 touchesCancelled(with:) 中處理取消;事件方法中只更新私有的追蹤狀態並設定 state;在動作方法中回應,而不是在事件回呼中;為動作方法快取原始的 NSEvent 或 NSTouch;「Override reset() to clear your cached state, so it doesn’t leak from one gesture into the next」(覆寫 reset() 清除快取的狀態,避免它從一個手勢洩漏到下一個);「Always end an active gesture recognizer by transitioning to NSGestureRecognizer.State.ended, NSGestureRecognizer.State.failed, or NSGestureRecognizer.State.cancelled. Otherwise, other recognizers can’t activate」(一定要讓進行中的手勢辨識器轉換到 ended、failed 或 cancelled 來結束,否則其他辨識器無法啟動);以及覆寫 location(in:),因為「If you skip this override and build your app with the macOS 27 SDK and later, AppKit returns NSZeroPoint and logs a console error.」(如果略過這個覆寫,又以 macOS 27 或更新的 SDK 建置 app,AppKit 會回傳 NSZeroPoint 並在主控台記錄錯誤。)1 接下來的 DragTrackingRecognizer 範例只是骨架:它的滑鼠按下、拖曳與放開覆寫,內容都是註解,指示您呼叫同一個私有的 track(_:event:state:) 方法;取消覆寫的註解要您設定 .cancelled;動作方法則讀回快取的位置與事件。1

違反契約的後果,在第二篇文章中有了具體的數字。「A gesture recognizer is stuck if it hasn’t reached a terminal state within 2 seconds after the person stops interacting with your app, such as when they release the mouse button or lift their fingers.」(使用者停止與 app 互動後,例如放開滑鼠按鈕或抬起手指,手勢辨識器若在 2 秒內沒有抵達終止狀態,就算是卡住了。)2 在排他視圖下,「A stuck gesture recognizer keeps holding its exclusive view」(卡住的手勢辨識器會一直占住它的排他視圖),使它的階層以外無法收集辨識器,而系統的補救是自動的:「To keep a single stuck gesture recognizer from blocking your app indefinitely, the system automatically transitions it to the NSGestureRecognizer.State.cancelled state, allowing gesture gathering to resume normally.」(為了避免單一卡住的辨識器無限期地擋住 app,系統會自動把它轉換到 cancelled 狀態,讓手勢收集恢復正常。)2 這就是表格第四列背後的機制,也意味著,已出貨的 app 裡一個壞掉的辨識器,會擋住它的排他階層以外的手勢收集,使 app 的其餘部分看起來沒有回應,直到使用者停止互動兩秒後自動取消為止,而動作的目標仍然要負責處理這次取消。

開發期間,有兩個使用者預設值能讓這些問題以當掉加上堆疊追蹤的方式現形,一個針對缺少的覆寫,一個取代無聲的取消。「Set the NSGestureRecognizerCrashOnMissingOverrides key in UserDefaults to YES」(在 UserDefaults 中把 NSGestureRecognizerCrashOnMissingOverrides 鍵設為 YES),或以啟動引數傳入 -NSGestureRecognizerCrashOnMissingOverrides YES,就會在缺少應該覆寫的事件方法時當掉;把 NSCrashOnStuckGestureTimeout 設為「to a small number of seconds, such as 2」(一個很小的秒數,例如 2),或傳入 -NSCrashOnStuckGestureTimeout 2,就會讓卡住的辨識器當掉,而不是被取消。2 第一個預設值並不能抓到所有問題:它「doesn’t detect a gesture recognizer that correctly transitions to NSGestureRecognizer.State.cancelled and sends its action, if the action’s target doesn’t handle the canceled state.」(如果手勢辨識器正確轉換到 cancelled 並送出動作,但動作的目標沒有處理取消狀態,它偵測不到。)2 接下來的警告適合放進檢查清單:「Comment out or remove NSGestureRecognizerCrashOnMissingOverrides and NSCrashOnStuckGestureTimeout before shipping your app」(出貨前請把這兩個設定註解掉或移除),以及「Changes to these user defaults or launch arguments take effect only after you rebuild or relaunch your app.」(變更這些使用者預設值或啟動引數,要在重新建置或重新啟動 app 之後才會生效。)2 這兩個預設值都沒有出現在議程或 6 月的技術說明中。

同一篇文章也說明了兩種非成功的終止狀態有何不同:.failed 用於從未比對成功的辨識器,而「If a gesture recognizer sets this after it already delivered an action, the system automatically converts it to NSGestureRecognizer.State.cancelled instead.」(如果手勢辨識器在已經送出動作之後才設定這個狀態,系統會自動改為 cancelled。)2 它還點名了議程中 Exposé 例子以外的另一個系統取消原因:「when the same touches begin a system-level gesture like smartMagnify(with:), the system takes over those touches for its own use.」(當同一組觸控開始了 smartMagnify(with:) 這類系統層級的手勢時,系統會接手這些觸控供自己使用。)2

同一頁還有三個較小的事實。辨識器可以延遲回應者事件:「If you set delaysPrimaryMouseButtonEvents to true, the system stores the relevant events until the gesture recognizer fails, then delivers them to the appropriate responders. If the gesture recognizer reaches the NSGestureRecognizer.State.ended state, the system never delivers the events.」(若設為 true,系統會先保存相關事件,直到手勢辨識器失敗才送給適當的回應者;如果手勢辨識器抵達 ended 狀態,系統就永遠不會送出這些事件。)2 回應者自己的觸控方法只適用於觸控式軌跡板:「These methods don’t have an equivalent delay mechanism, and they apply only to indirect touches from a trackpad.」(這些方法沒有對應的延遲機制,而且只適用於來自觸控式軌跡板的間接觸控。)2 視圖控制器不能持有辨識器:「You can’t attach a gesture recognizer to a view controller directly」(不能把手勢辨識器直接掛在視圖控制器上),所以要把它加到控制器的視圖上,並以控制器作為目標與代理。2

排他視圖:預設開啟,在 Xcode 的 AppKit 範本中關閉

下午那篇文章根據議程與 6 月的技術說明解釋了排他性。這兩篇文章補上了預設值、機制,以及一個關於 Xcode 的事實。

機制是 macOS 27 新增的:「In macOS 27 and later, the initial hit-tested view for a mouse click or touch event becomes an exclusive view for gathering gesture recognizers」(在 macOS 27 及更新版本中,滑鼠點按或觸控事件最初點擊測試命中的視圖,會成為收集手勢辨識器的排他視圖),以及「This behavior simulates the presence of a single mouse pointer, even when a person touches a Mac with a touch display with several fingers at once.」(即使使用者以多根手指同時觸碰觸控螢幕 Mac,這種行為也會模擬只有一個滑鼠指標的情況。)2 預設值是:「Exclusive views are on by default」(排他視圖預設開啟),以及「If your app’s information property list doesn’t include the NSViewGestureRecognizerIsExclusive key, the system treats it as YES.」(如果 app 的 Info.plist 不包含這個鍵,系統會把它視為 YES。)2 這個鍵自己的頁面也一致,寫著「The default value is YES」,並把退出的情況限定得很窄:「Set this key to NO to allow gesture recognizers in multiple views to activate simultaneously. Do this only if your app intentionally needs concurrent gesture recognition across different views.」(設為 NO 可讓多個視圖中的手勢辨識器同時啟動;只有在 app 刻意需要跨視圖同時辨識手勢時才這麼做。)6 系統逐一視圖退出的例子是顏色面板:「AppKit uses this same technique in the standard Color Panel」(AppKit 在標準的顏色面板中使用了同樣的技巧),那裡有好幾個滑桿可以同時移動。2

關於 Xcode 的事實,兩篇文章都有寫:「Xcode adds the NSViewGestureRecognizerIsExclusive key with a value of NO to new macOS app projects, so those apps don’t use exclusive views unless you change the value to YES.」(Xcode 會在新的 macOS app 專案中加入值為 NO 的這個鍵,所以這些 app 除非把值改為 YES,否則不使用排他視圖。)1 我讀了這台 Mac 上的專案範本,看看這涵蓋了哪些專案(作者分析)。在 Xcode 27.1(27A9275)與 Xcode 27.2 beta(27B5019j)中,macOS App 範本繼承自一個「Cocoa App Base」單元,它的 app 生命週期選項有 SwiftUI 與 Cocoa 兩個值,預設為 SwiftUI;值為 NO 的 INFOPLIST_KEY_NSViewGestureRecognizerIsExclusive 鍵只位於 Cocoa 單元之內,所以 Xcode 只在專案使用 AppKit app delegate 生命週期時寫入它(以建置設定的形式,最後落在產生的 Info.plist 中),使用 SwiftUI 生命週期時則不會。7 生命週期選項在範本中沒有顯示名稱;有顯示名稱的選項是 Interface:(SwiftUI、Storyboard 或 XIB,預設為 SwiftUI),而 Storyboard 與 XIB 單元要求 AppKit 生命週期,因此 Storyboard 或 XIB 專案會得到這個鍵,SwiftUI 介面的專案則只有在生命週期為 AppKit app delegate 時才會得到。7 Document App 範本衍生自同一個基礎,所以在相同條件下會繼承那個建置設定,同時也以 Info.plist 項目的形式把這個鍵寫成 false;macOS Game 範本強制使用 Cocoa 生命週期,所以新建的 macOS 遊戲一開始就是非排他的。7 這台 Mac 上沒有比 27.1 更舊的 Xcode,所以我無法說出範本是何時加入這個鍵的。實務上的解讀是:用這些範本建立的 AppKit 專案一開始是非排他的;SwiftUI 專案,以及 Info.plist 中沒有這個鍵的任何既有 app,則是排他的。第二篇文章的遷移附註正是由此而來:如果您的 app 把這個鍵設為 NO,在取代追蹤迴圈時,「also set the view’s exclusiveGestureBehavior to NSView.ExclusiveGestureBehavior.exclusive」(也要把該視圖的 exclusiveGestureBehavior 設為 exclusive),因為「This prevents simultaneous gesture recognition with unrelated views, which the loop previously prevented implicitly.」(這能避免與無關的視圖同時辨識手勢,而這原本是迴圈在暗中防止的。)2

第一篇文章釐清了與排他性和捲動有關的三個設定,用的是這句話:「These settings don’t override each other.」(這些設定不會互相覆蓋。)1 exclusiveGestureBehavior 與 plist 鍵決定 AppKit 收集哪些辨識器,而且「The view’s setting applies to that view and its subviews, and takes precedence over the app-wide key.」(視圖的設定適用於該視圖及其子視圖,並優先於整個 app 的鍵。)1 isCancellableByScrollGesture 以辨識器為單位,彼此獨立:「It lets a scroll gesture cancel that gesture recognizer even if it has already begun.」(即使該手勢辨識器已經開始,捲動手勢也能取消它。)1 scrollGestureForRelationships 用於與捲動視圖自身辨識器競爭的辨識器。1 文章提醒要留意的情境,是捲動視圖中的按鈕:在它的辨識器上把 isCancellableByScrollGesture 設為 true,讓從按鈕上開始的拖曳變成捲動,而不是點按。1

文字引擎還多了一項工具。NSTextSelectionManager「creates its gesture recognizers itself, so you don’t have a reference to them」(會自行建立手勢辨識器,所以您拿不到它們的參照),因此必須等待它們的自訂辨識器,要在管理器的 gesturesForFailureRequirements 包含另一個辨識器時,從 gestureRecognizer(_:shouldRequireFailureOf:) 回傳 true。1 這個屬性列為 macOS 27.0,並宣告於 macOS 27.0 SDK 的 NSTextSelectionManager.h 第 59 行(作者分析)。67

模擬,五個項目

下午那篇文章引用了 6 月技術說明中,針對非 AppKit UI 框架的四條模擬規則。文章的清單有五條,新增的是拖曳。18

  • 「Tapping emulates a mouse-down followed immediately by a mouse-up.」(點一下模擬滑鼠按下後立即放開。)
  • 「Touching and then immediately panning emulates a trackpad scroll, even if the UI doesn’t otherwise handle Multi-Touch input directly.」(觸碰後立即平移模擬觸控式軌跡板捲動,即使 UI 本身並不直接處理多點觸控輸入。)
  • 「Touching, briefly holding, and then moving emulates mouseDown(with:), followed by mouseDragged(with:) as it moves, and mouseUp(with:) on lift.」(觸碰、短暫按住再移動,模擬 mouseDown(with:),移動時接著 mouseDragged(with:),抬起時 mouseUp(with:)。)
  • 「Touching and holding without movement emulates rightMouseDown(with:), and rightMouseUp(with:) on lift.」(觸碰並按住不動模擬 rightMouseDown(with:),抬起時 rightMouseUp(with:)。)
  • 「Pinching or rotating with two fingers emulates the trackpad magnify and rotate gestures.」(雙指捏合或旋轉模擬觸控式軌跡板的放大與旋轉手勢。)

第三項是議程中「drag with a small hold delay」(11:31)的成文版本:立即移動的觸控是捲動,先按住再移動的觸控是拖曳。9 6 月的清單有點一下、立即平移、長按,以及雙指捏合或旋轉,沒有拖曳這一項。8 上限是兩根手指:「This emulation covers interactions with one or two fingers. If another finger touches down during a pinch or rotation, the emulation ignores it and keeps tracking the original two fingers.」(這種模擬涵蓋一指或兩指的互動;如果在捏合或旋轉期間有另一根手指碰觸,模擬會忽略它,繼續追蹤原本的兩根手指。)1 三指以上需要辨識器;一旦框架自行處理觸控,把 NSIsTouchNative 設為 YES 就會關閉額外的模擬。1

遊戲:TN3216

這份技術說明是寫給遊戲的:「This guidance focuses on configurations common in games, such as an app with a single view backed by a CAMetalLayer.」(本指引著重於遊戲常見的配置,例如只有一個以 CAMetalLayer 為底的視圖的 app。)3 它的設計框架比 HIG 更嚴格:「Touch extends the Mac experience rather than replacing other input」(觸控是擴充 Mac 的體驗,而不是取代其他輸入),以及「Touch works best for brief interactions that complement these devices」(觸控最適合用於輔助這些裝置的短暫互動),並指示「Design touch interactions for brief moments that the player can complete with one hand, while the other hand stays on the keyboard, mouse, or controller」(把觸控互動設計成玩家能用一隻手完成的短暫時刻,另一隻手則留在鍵盤、滑鼠或控制器上),適用的互動「such as inventory management, map zoom and scroll, or the settings menu」(例如道具管理、地圖縮放與捲動,或設定選單)。3

相容性的部分,是同樣的模擬再加上遊戲專屬的變化。「AppKit applies game-specific emulation to apps in a games category, and can apply emulation to an individual view, such as one that hosts a third-party UI framework.」(AppKit 會對遊戲類別的 app 套用遊戲專屬的模擬,也能對個別視圖套用模擬,例如承載第三方 UI 框架的視圖。)3 類別從 Info.plist 讀取:如果 LSApplicationCategoryType 是 public.app-category.games 或某個遊戲子類別,「the system applies game-specific mouse emulation」(系統會套用遊戲專屬的滑鼠模擬)。3 它解決的是滑鼠從來不會有的問題:「A mouse moves continuously from one position to the next, but a player can tap or start a drag at two distant locations in succession」(滑鼠從一個位置連續移動到下一個位置,但玩家可以接連在兩個相距很遠的位置點一下或開始拖曳),所以累加位移量的遊戲會漂移,而「For games, AppKit sends a mouseMoved event to the new location before each mouseDown(with:), so your game can refresh its cached mouse position.」(對遊戲而言,AppKit 會在每次 mouseDown(with:) 之前送出一個移到新位置的 mouseMoved 事件,讓遊戲可以更新快取的滑鼠位置。)3

接著,技術說明依序列出模擬的事件。點一下:mouseMoved(with:)「from the last mouse position to the tap location」(從上一個滑鼠位置移到點按的位置),接著 mouseDown(with:),然後 mouseUp(with:)。拖曳:mouseMoved(with:) 移到拖曳開始的位置,mouseDown(with:),mouseDragged(with:)「repeatedly while the touch continues」(在觸控持續期間重複送出),以及 mouseUp(with:)。3 然後是一個「Important」方塊中的規則:「Don’t intercept events by overriding sendEvent(:) or pulling them with nextEvent(matching:until:inMode:dequeue:). Doing so can interfere with gesture recognizers that respond to a physical mouse.」(不要以覆寫 sendEvent(:) 或用 nextEvent(matching:until:inMode:dequeue:) 拉取的方式攔截事件,這樣可能會干擾回應實體滑鼠的手勢辨識器。)3

議程中完全沒有對應的部分,是 GCMouse。「By default, the system presents each touch surface, whether a Mac with a touch display or a Sidecar-connected iPad, as an additional GCMouse that emulates mouse input from touches」(預設情況下,系統會把每個觸控表面,不論是觸控螢幕 Mac 還是透過 Sidecar 連接的 iPad,都呈現為一個額外的 GCMouse,以觸控模擬滑鼠輸入):手指放下就按下左鍵,移動時以位移量呼叫滑鼠移動處理常式,抬起就放開。3 它的限制是:「The emulated GCMouse ignores additional fingers, and it doesn’t emulate other buttons, scrolling, or pinch input」(模擬的 GCMouse 會忽略額外的手指,也不模擬其他按鈕、捲動或捏合輸入),而且因為觸控會落在彼此不連續的位置,「Don’t use them to track an absolute pointer position.」(不要用它們來追蹤指標的絕對位置。)3

NSIsTouchNative 對遊戲的作用比對 AppKit app 更大。設為 YES 後,「AppKit’s emulation will stick to a generic emulation - ignoring the app’s category or whether it uses a third-party UI framework - and disable GCMouse emulation」(AppKit 的模擬會維持通用模擬,不理會 app 的類別或是否使用第三方 UI 框架,並停用 GCMouse 模擬),所以觸控表面不再以 GCMouse 出現,而且「Your game receives touch through gesture recognizers instead of emulated mouse events.」(您的遊戲會透過手勢辨識器接收觸控,而不是模擬的滑鼠事件。)3 仍有一點殘留:「AppKit still emulates mouse events for any NSView that handles them, for example by overriding mouseDown:.」(對於任何處理滑鼠事件的 NSView,例如覆寫了 mouseDown: 的視圖,AppKit 仍會模擬滑鼠事件。)3 這個鍵自己的頁面說大多數 AppKit app 不需要它,它「primarily intended for apps that embed a non-AppKit rendering or input framework and implement their own touch-handling pipeline」(主要是給內嵌非 AppKit 算繪或輸入框架、自行實作觸控處理流程的 app 使用);遊戲引擎正是這種情況。6

實作一節是以 Swift 與 Objective-C 寫成的代理模式:一個平移與一個點按辨識器,buttonMask 設為 0 以忽略實體滑鼠;一個放大辨識器;shouldRecognizeSimultaneouslyWith 只對平移與捏合這一對回傳 true;shouldRequireFailureOf 則讓點按等待兩者失敗。3 一則附註確認了第一篇文章所說的預設值:「The gesture recognizers must include direct in allowedTouchTypes. On macOS 27 and later, this is the default.」(手勢辨識器的 allowedTouchTypes 必須包含 direct;在 macOS 27 及更新版本中,這就是預設值。)3 路由的建議是照引擎的形狀來設計:「Install the gesture recognizers once in your app and leave them enabled」(在 app 中只安裝一次手勢辨識器,並讓它們保持啟用),把回呼轉換成引擎的輸入事件,並且「Let the active input handler decide if and how to react to different gestures」(讓當前的輸入處理常式決定是否以及如何回應不同的手勢),其中平移處理常式以 backing scale factor 把點轉換成像素,並為原點在左上角的引擎翻轉 y 軸。3 切換之後,標準控制項照常運作,「as long as you don’t attach your own direct-touch gesture recognizers to them」(只要您沒有在它們身上掛自己的直接觸控手勢辨識器)。3

最後的檢查清單分為兩半。仰賴模擬的遊戲:從每個事件讀取位置,不要覆寫 sendEvent:,也不要從 nextEventMatchingMask: 拉取事件。自行處理觸控的遊戲:設定 NSIsTouchNative,使用 buttonMask 為 0 的直接觸控辨識器,在玩家預期的地方加入觸控並讓它保持單手可操作,「Continue to read GCMouse for the physical mouse. With NSIsTouchNative set, touch no longer appears as an emulated GCMouse」(實體滑鼠繼續從 GCMouse 讀取;設定 NSIsTouchNative 後,觸控不再以模擬的 GCMouse 出現),並盡可能使用標準控制項。3

HIG,日期為 10 月 28 日

下午那篇文章說,登陸頁面打算連結的四個 HIG 頁面,都沒有關於 Mac 螢幕觸控的指引。現在其中三個有了,各自帶著日期為 2026 年 10 月 28 日的變更記錄;Live Activities 頁面則沒有變動。4

Designing for macOS(「Added guidance for touch input.」)改寫了平台描述中的三行,並新增一條最佳做法。人體工學:「On a Mac with a touch display, people reach forward to touch the screen, so they tend to use touch for brief, intentional interactions rather than sustained work.」(在觸控螢幕 Mac 上,使用者要把手往前伸才能觸碰螢幕,所以傾向把觸控用在短暫、有意圖的互動,而不是長時間的工作。)輸入方式現在以鍵盤與指標裝置為首,並把「Gestures on a trackpad or a Mac with a touch display」列在其他方式之中。新的最佳做法是把整場設計議程濃縮成一句話:「Support standard gestures on trackpads and Mac devices with a touch display. Use larger tap targets and controls that expand on touch to make touch interaction easier, while keeping control size and density the same to stay optimized for keyboard and pointing device input.」(在觸控式軌跡板與觸控螢幕 Mac 上支援標準手勢;使用較大的點按目標與觸控時會放大的控制項,讓觸控互動更容易,同時維持控制項的尺寸與密度不變,以保持對鍵盤與指標裝置輸入的最佳化。)4

Gestures(「Added guidance for gestures and touch input in macOS.」)擴充了它的 macOS 一節,現在為開發者指引連結到兩篇 AppKit 文章。第一句話就定下了地位:「On a Mac with a touch display, or on an iPad connected with Sidecar, people can also interact directly using touch gestures as a secondary input mode.」(在觸控螢幕 Mac 上,或在透過 Sidecar 連接的 iPad 上,使用者也可以把觸控手勢當作次要的輸入模式直接互動。)4 接著是六條規則,每條都以粗體開頭:

  • 「Keep the layout and size of interface elements the same when you support touch.」(支援觸控時,介面元素的版面與尺寸維持不變。)理由是:「In macOS, touch is secondary to keyboard and pointing device input」(在 macOS 中,觸控次於鍵盤與指標裝置輸入),而且使用者經常在不同輸入之間切換。與 iPhone Duo 的對比正是重點,在那裡,內螢幕會改變 size class:Mac 改變的是輸入方式,版面則不變。
  • 「Make tap targets bigger, not the controls themselves」(讓點按目標變大,而不是控制項本身變大),做法是「enlarging their tap targets to anticipate that interaction may start slightly outside of their visual bounds」(擴大點按目標,預期互動可能從視覺邊界稍外的地方開始),以及「Where possible, use standard framework controls, which the system configures with an optimal tap target size automatically.」(盡可能使用標準框架控制項,系統會自動為它們設定最適當的點按目標尺寸。)
  • 「Consider providing expandable controls」(考慮提供可放大的控制項),也就是點一下時出現、按住時保持、並在使用者抬起手指後仍會「for a short time after they lift their finger to keep the control accessible to touch」(停留一小段時間,讓控制項仍可用觸控操作)的暫時性觸控尺寸版本;「When using standard system controls, steppers, sliders, and dials gain this behavior automatically.」(使用標準系統控制項時,步進器、滑桿與轉盤會自動獲得這種行為。)
  • 「Don’t use touch as the only way to access controls or features」(不要讓觸控成為使用控制項或功能的唯一途徑),反過來也一樣:「aim to provide touch equivalents for actions otherwise performed exclusively with a pointing device, like using a keyboard modifier to shift-drag or displaying a menu when the pointer moves over an element.」(對於原本只能用指標裝置完成的動作,例如以鍵盤修飾鍵做 Shift 拖曳,或在指標移到元素上時顯示選單,盡量提供對應的觸控操作。)
  • 「Where possible, align with gestures people already know」(盡可能與使用者已經熟悉的手勢一致),來源可以是您的 iPhone 或 iPad app,或觸控式軌跡板,並且「Test to ensure they don’t conflict with macOS system gestures.」(測試確認它們不會與 macOS 系統手勢衝突。)
  • 「Make sure custom gestures work for both touch and trackpad」(確保自訂手勢在觸控與觸控式軌跡板上都能運作),做法是「modifier keys where needed to help the gesture work for all touch inputs」(在需要時使用修飾鍵,讓手勢在所有觸控輸入下都能運作)。4

這個頁面沒有給出 36 點這個數字;那個數字出自兩場議程(設計議程 9:17 的「Touch on macOS uses a touch target of 36 points」,以及 AppKit 議程 5:53 把點按矩形擴大到「a minimum of 36 points on each axis」)。9 它新增了一張表格「macOS trackpad and touch display gestures」,為每個動作配上觸控式軌跡板與觸控的形式:選取是主要點按或點一下;開啟是按兩下或點兩下;捲動在觸控式軌跡板上是兩指,在螢幕上是一指;縮放與旋轉在兩者上都是相同的捏合與雙指轉動;情境選單是次要點按或觸碰並按住;拖曳是按住拖移,或「Touch and hold, then drag」(觸碰並按住,然後拖曳)。4 在跨平台的表格中,「Touch (or pinch) and hold」現在列出了 macOS,點兩下那一列則新增了「open an item on a Mac with a touch display」(在觸控螢幕 Mac 上開啟項目)。4

Pointing devices(「Added guidance for revealing hidden controls with touch on Mac, and updated guidance and artwork for iPadOS and macOS.」)是規則最具體的頁面,位於新的標題「Touch support for hidden controls」之下。前提是:「Some Mac apps reveal certain controls only when the pointer moves over them. These controls need another way to appear when someone uses touch on a Mac that supports it.」(有些 Mac app 只在指標移到某些控制項上時才顯示它們;當使用者在支援觸控的 Mac 上使用觸控時,這些控制項需要另一種出現方式。)4 規則如下:

  • 「Decide which hidden controls need touch support. Reserve touch support for essential controls.」(決定哪些隱藏控制項需要觸控支援,只為重要的控制項保留觸控支援。)工具提示不在此列,觸控已經能以其他方式完成的事也不在此列:「someone can swipe to scroll a carousel, so there’s no need to reveal the navigation buttons that exist for mice without a scroll wheel.」(使用者可以滑動來捲動輪播,所以不需要顯示那些為沒有滾輪的滑鼠而設的導覽按鈕。)還有:「Avoid keeping hidden controls visible all the time just to accommodate touch.」(不要只為了遷就觸控,就讓隱藏的控制項一直顯示。)
  • 「Reveal a hidden control when someone taps its general location on a touch display.」(使用者在觸控螢幕上點一下隱藏控制項的大致位置時,就顯示它。)只顯示相關的控制項,並且「Hide a control again when the person finishes with it, or after a few seconds if they don’t use it.」(使用者用完後再次隱藏,或在他們沒有使用的幾秒後隱藏。)
  • 「Retain the current selection when revealing a hidden control」(顯示隱藏控制項時保留目前的選取),因為控制項可能要對選取內容執行動作。
  • 「Make essential hidden controls available in a context menu」(讓重要的隱藏控制項也能從情境選單使用),觸控可以用觸碰並按住來開啟情境選單。
  • 「In a sidebar, reveal hidden controls when someone taps the corresponding section header or row.」(在側邊欄中,使用者點一下對應的區段標題或列時,就顯示隱藏的控制項。)例子是 Finder:「tapping the Favorites header in the Finder sidebar collapses the section and reveals a disclosure button.」(點一下 Finder 側邊欄的「喜好項目」標題,會收合該區段並顯示一個展開按鈕。)顯示出來的控制項會一直保留,直到觸控離開側邊欄或使用者改用指標裝置;而點一下隱藏控制項所在的區域只會顯示它,不會啟動它:「The control acts only when they tap it again.」(控制項要在使用者再點一次時才會動作。)
  • 「In a tabbed window, reveal the selected tab’s close button when someone taps anywhere in the window」(在有標籤頁的視窗中,使用者點一下視窗任何地方時,就顯示所選標籤頁的關閉按鈕),並保留它,直到使用者改回指標裝置為止。4

這六條規則是兩場議程懸停指引的成文版本,並補上了議程沒有給出的時間與例外:顯示出來的控制項幾秒後隱藏、點第二次才啟動,以及側邊欄與標籤頁關閉按鈕這兩種會保持顯示的情況。同一次修訂也為 Liquid Glass 改寫了頁面中關於 iPadOS 指標的內容,把文字中預設的指標形狀從圓形改為箭頭,把游標表格中只有 AppKit 的那一欄換成 AppKit 與 SwiftUI 的符號,並移除了指標磁吸那一節;這些都與 Mac 螢幕無關。4

仍然沒有到的東西

以下是 10 月 28 日晚上 9 點 51 分(PT)的檢查結果,比對對象包括 Apple 的文件端點、登陸頁面、開發者新聞與 releases feed,以及 Tech Talks 列表。56

  • Xcode 27.3 beta。登陸頁面的區塊仍標示「Coming soon」,releases feed 列出的項目沒有比 10 月 26 日的小版本更新更新的(其中包括 macOS 27.2),而 Xcode 27.3 與 macOS 27.3 的發行說明頁面都回傳 404。最新的 Xcode 仍是 27.2 RC,它的 SDK 我還沒有檢查;下午那篇文章關於部署目標的但書依然適用,Xcode 27 發布文章則有部署目標的背景說明。
  • AppKit 議程示範過的三個系統手勢開關:NSWindow.disablesDirectTouchSystemGestures、UIViewController.prefersDirectTouchSystemGesturesDisabled 與 SwiftUI 的 directTouchSystemGesturesDisabled()。三者都回傳 404,兩篇新文章也都沒有提到它們。文章說明了系統手勢如何取消您的辨識器,但沒有說明如何關閉系統手勢。
  • 可在 macOS 上使用的 ActivityKit。框架頁面帶著 10 月 8 日的時間戳,沒有列出 macOS 平台,而 Live Activities 議程中的 AlertConfiguration.announcement(_:) 回傳 404。下午那篇文章關於 Live Activities 的段落維持原樣,狀態機那篇文章也仍然只是 iOS 的實作。
  • AppKit 議程在 12:16 提到的「gesture modernization skill」。沒有任何 Apple 頁面點名它;登陸頁面的 TK skill 註解仍在。59
  • 從議程頁面連到這篇文章的連結,以及 Apple 對於以模擬器或 Device Hub 測試 Mac 觸控的任何說明。
  • SwiftUI 文件更新頁面上一則有日期的條目。它的伺服器時間戳是 10 月 28 日下午 2 點 58 分(PT),但最新的一節仍是 2026 年 9 月,與手勢相關的條目仍是 2026 年 6 月那一行 GestureInputKinds;不論改了什麼,都不是新增一節有日期的內容。6

對開發者那篇文章的更正

下午那篇文章在中午檢查時是正確的;到了 10 月 28 日晚上 9 點 51 分(PT)的檢查,其中五項陳述已不再成立。那篇文章中與本文同時發布的一則更新區塊連結到這裡,那些陳述也已就地更正;以下是清單。

  1. 登陸頁面三個「Coming soon」區塊,現在只剩一個。文件資源與設計資源已經上線;Xcode 27.3 beta 還沒有。5
  2. 「Adopting gesture recognizers in your app」不再回傳 404。它已經上線,遷移指引應以它為準。1
  3. HIG 頁面不再對 Mac 螢幕的觸控保持沉默。四個頁面中有三個帶著 10 月 28 日的變更記錄;Live Activities 頁面則沒有。4
  4. 模擬清單有五條規則,而不是 6 月技術說明的四條;新增的是先按住再拖曳的規則。18
  5. 排他視圖只對 Info.plist 中缺少這個鍵的 app 預設開啟。在 Xcode 27.1 與 27.2 beta 中,Xcode 的 macOS App 與 Document App 範本會為 app 生命週期為 AppKit app delegate 的專案,把這個鍵寫成 NO。127

下午那篇文章關於 SDK、三個沒有文件的開關與 ActivityKit 的說法,並未因此後發布的任何內容而改變。

重點整理

給 AppKit 開發者: 在 Sidecar 上執行文章的四行測試,再搜尋它點名的五個符號;控制項子類別只要覆寫了 mouseDown(with:)、mouseDragged(with:) 或 mouseUp(with:),不論是否呼叫 super,該控制項都會退回追蹤迴圈,修正方式是以 NSControl.Events 建立 target-action 配對,並把 .trackingEndedOutside 放進隱藏集合。1

給保留的迴圈: 僅限觸控的辨識器需要 buttonMask 0,否則滑鼠拖曳會啟動辨識器,而您的 mouseDown(with:) 永遠不會執行;allowedTouchTypes = .direct 本來就是預設值。1

給自訂辨識器: 抵達終止狀態,覆寫 reset() 與 location(in:),除錯時使用 -NSGestureRecognizerCrashOnMissingOverrides YES 與 -NSCrashOnStuckGestureTimeout 2,出貨前移除;在正式環境中,卡住的辨識器會在使用者停止互動兩秒後被取消。12

給排他性: 沒有這個鍵的 app 是排他的;以 Xcode 的 macOS App 範本搭配 AppKit app delegate 生命週期建立的專案(所有 Storyboard 或 XIB 專案)不是,而使用預設 SwiftUI 生命週期的專案則是。遷移追蹤迴圈之前,先讀一下您的 Info.plist。27

給遊戲: 從事件讀取位置,而不是累加位移量;不要覆寫 sendEvent(_:);並且要知道,在 NSIsTouchNative 設為 YES 之前,每個觸控表面都是一個額外的模擬 GCMouse。3

給設計師: 觸控是「a secondary input mode」(次要的輸入模式);維持版面與控制項尺寸;擴大點按目標,而不是控制項;在使用者點一下隱藏控制項附近時顯示它們,使用者用完後或閒置幾秒後隱藏,側邊欄與標籤頁的關閉按鈕是例外,並為重要的控制項提供情境選單。4

常見問題

AppKit 的遷移文章上線了嗎?

上線了。「Adopting gesture recognizers in your app」在 10 月 28 日中午(PT)回傳 404,到了晚上 9 點 51 分已經上線,列在 AppKit 的 Essentials 與 Gestures 兩節中,也從登陸頁面的文件區塊連結過去。15

我的 app 開啟了排他視圖嗎?

如果您的 Info.plist 沒有 NSViewGestureRecognizerIsExclusive 鍵,答案是肯定的:「If your app’s information property list doesn’t include the NSViewGestureRecognizerIsExclusive key, the system treats it as YES.」(如果 app 的 Info.plist 不包含這個鍵,系統會把它視為 YES。)2 在 Xcode 27.1 或 27.2 beta 中,以 macOS App 範本搭配 AppKit app delegate 生命週期建立的專案(所有 Storyboard 或 XIB 專案),會以建置設定的形式得到值為 NO 的這個鍵;同一個範本搭配預設的 SwiftUI 生命週期,則不會寫入任何鍵(依這台 Mac 上的範本所做的作者分析)。7

永遠不結束的手勢辨識器會怎樣?

它會一直占住它的排他視圖,直到系統把它取消:辨識器「is stuck if it hasn’t reached a terminal state within 2 seconds after the person stops interacting with your app」(在使用者停止與 app 互動後 2 秒內沒有抵達終止狀態,就算卡住了),而系統會「automatically transitions it to the NSGestureRecognizer.State.cancelled state」(自動把它轉換到 cancelled 狀態)。2 除錯時設定 NSCrashOnStuckGestureTimeout 預設值,就能改為取得堆疊追蹤。2

為什麼加上平移辨識器之後,我的追蹤迴圈就收不到拖曳了?

因為辨識器的 buttonMask 維持預設值,占用了滑鼠主按鈕:「A mouse drag then begins the gesture recognizer, and AppKit doesn’t call your mouseDown(with:) override, so the tracking loop doesn’t run.」(滑鼠拖曳會啟動手勢辨識器,AppKit 不會呼叫您的 mouseDown(with:) 覆寫,追蹤迴圈因此不會執行。)把 buttonMask 設為 0,讓它成為僅限觸控的辨識器。1

遊戲在 Mac 上如何接收觸控?

以模擬的滑鼠事件接收,順序有文件記載,每次 mouseDown(with:) 之前都會有一個移到新位置的 mouseMoved(with:);同時也以一個額外的 GCMouse 接收,它的位移量並不連續。把 NSIsTouchNative 設為 YES,遊戲就會改用手勢辨識器,模擬的 GCMouse 也會移除。3

HIG 有給出 Mac 的觸控目標尺寸嗎?

沒有。Gestures 頁面說「Make tap targets bigger, not the controls themselves」(讓點按目標變大,而不是控制項本身變大),尺寸則交給框架控制項處理;36 點這個數字出自兩場議程,而不是 HIG。49

本文的事實出自哪裡?

Apple 的兩篇 AppKit 文章、TN3216、三個 HIG 頁面與登陸頁面,全部於 2026 年 10 月 28 日晚上 9 點 51 分(PT)抓取,並把文章是否可取得、HIG 頁面與登陸頁面,與同一天中午及上午 11 點 49 分的檢查結果比對;文中點名之每個符號的 Apple 文件 JSON;用於比對的 6 月技術說明;議程引文所依據的 Tech Talks 字幕軌;以及我這台 Mac 上 Xcode 27.1 與 Xcode 27.2 beta 內的標頭檔與專案範本,這部分屬於作者分析。媒體報導不列為來源。

資料來源


  1. Apple Developer Documentation,Adopting gesture recognizers in your app,AppKit。2026 年 10 月 28 日晚上 9 點 51 分(PT)抓取(HTTP 200,Last-Modified: Wed, 28 Oct 2026 21:30:45 GMT);同一個 URL 與其 JSON 端點在同一天中午(PT)回傳 404。列在 AppKit 框架頁面的 Essentials 一節,排在「Adopting Liquid Glass」之後的第二位,也列在 Gestures 的「Gesture handling」之下。Wayback Machine 的第一次擷取為 2026-10-29 01:08:12 UTC(CDX 列 20261029010812,狀態 200;原始標頭與 CDX 憑據與本文的證據一併保存)。引用的章節:Overview、「Prepare your app for a Mac with a touch display」(表格、四行測試、搜尋清單)、「Filter gesture recognizer candidates」、「Handle touch input」、「Update your event-handling code」、「Replace common mouse-down overrides」、「Handle tracking-loop compatibility」、「Replace your custom tracking loops with gesture recognizers」、「Add touch support to your custom tracking loop」、「Write a custom gesture recognizer」、「Account for gesture recognizer behavior changes」與「Support non-AppKit UI frameworks」。本文的 Swift 程式碼清單,是文章「Add touch support to your custom tracking loop」清單縮減成的四行版本。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  2. Apple Developer Documentation,Handling AppKit gestures,AppKit。2026 年 10 月 28 日晚上 9 點 51 分(PT)抓取(Last-Modified: Wed, 28 Oct 2026 05:35:41 GMT);頁面上沒有修訂記錄;TN3212 沒有連結到它;截至當時,Wayback Machine 的索引中沒有它的擷取。引用的章節:Overview、「Track gesture recognizer states」、「Understand gesture recognizer gathering」、「Implement a custom gesture recognizer」、「Prioritize gesture recognizers」、「Delay responder events」、「Configure exclusive views」、「Replace event-tracking loops」與「Diagnose unresponsive gesture recognizers」。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  3. Apple Developer Documentation,TN3216: Supporting touch input for games on macOS,修訂記錄「2026-10-28 First published」,2026 年 10 月 28 日晚上 9 點 51 分(PT)抓取(Last-Modified: Wed, 28 Oct 2026 21:09:50 GMT)。引用的章節:Overview、「Extend the player experience meaningfully」、「Understand automatic touch support in existing games」、「Understand GCMouse emulation」、「Opt out of mouse emulation」、「Implement touch events」、「Route gestures in your game engine」、「Use standard AppKit controls for automatic touch support」與「Review your touch integration」。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  4. Apple Developer,Human Interface Guidelines,2026 年 10 月 28 日晚上 9 點 51 分(PT)抓取,並與同一天中午(PT)抓取的版本比對:Designing for macOS,變更記錄「October 28, 2026 | Added guidance for touch input.」;Gestures,變更記錄「October 28, 2026 | Added guidance for gestures and touch input in macOS.」,含 macOS 平台一節與表格「macOS trackpad and touch display gestures」;Pointing devices,變更記錄「October 28, 2026 | Added guidance for revealing hidden controls with touch on Mac, and updated guidance and artwork for iPadOS and macOS.」,含「Touch support for hidden controls」一節;Live Activities,與中午的版本逐位元組相同,變更記錄止於 2026 年 10 月 13 日。前三個頁面的中午版本都不包含任何引用的文字。新 Gestures JSON 的伺服器 Last-Modified 標頭為 Tue, 27 Oct 2026 05:11:02 GMT,晚上 9 點 59 分的第一次請求帶有 X-Cache: hit-stale。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  5. Apple Developer,Get ready for Touch on Mac,2026 年 10 月 28 日上午 11 點 49 分(PT)抓取(三個區塊標示「Coming soon」,連結保留在 HTML 註解中),以及晚上 9 點 51 分(PT)抓取:「Xcode 27.3 beta」/「Download the latest SDKs to build, run, and test your app.」/「Coming soon」;「Documentation resources」/「Learn how to build and optimize your app for touch input.」,連結到文章與範例;「Design resources」/「Create experiences that take advantage of touch user interface patterns.」,連結到 Gestures、Designing for macOS、Pointing devices 與 Apple Design Resources,後者的產品外框清單中包含「MacBook Pro PROLED XDR Display」;兩次抓取中都有註解 TK skill。Latest News RSS,最新一則為「Say hello to Touch on Mac」,Wed, 28 Oct 2026 10:00:55 PDT;Releases RSS,最新項目的日期為 Mon, 26 Oct 2026 10:00:00 PDT(iOS、iPadOS、macOS 27.2(26B123)、tvOS、visionOS 與 watchOS 27.2);Tech Talks 列出 111456、111467 與 111468,沒有其他關於觸控或 Live Activities 的議程;Bring Touch to your Mac app 的頁面,沒有連結到任何一篇文章;全部於晚上 9 點 51 分(PT)抓取。 ↩↩↩↩↩↩↩↩↩

  6. Apple 文件 JSON(developer.apple.com/tutorials/data/documentation/<path>.json),2026 年 10 月 28 日晚上 9 點 51 分(PT)抓取。找到者:trackingEndedOutside(macOS 11.0)、NSEvent.isTouchSwipeNavigationEnabled(macOS 27.0)、NSTextSelectionManager.gesturesForFailureRequirements(macOS 27.0)、NSGestureRecognizerDelegate 上的 gestureRecognizer(_:shouldRequireFailureOf:)(macOS 10.10)、NSViewGestureRecognizerIsExclusive(「The default value is YES.」)、NSIsTouchNative、Mouse, Keyboard, and Trackpad(關於回應者鏈與觸控的兩句話)、Supporting drag and drop through file promises(關於直接觸控的那句話),以及 SwiftUI updates(Last-Modified: Wed, 28 Oct 2026 21:58:06 GMT,最新一節為「September 2026」)。找不到者(HTTP 404):appkit/nswindow/disablesdirecttouchsystemgestures、uikit/uiviewcontroller/prefersdirecttouchsystemgesturesdisabled、swiftui/view/directtouchsystemgesturesdisabled()、activitykit/alertconfiguration/announcement(_:)、xcode-release-notes/xcode-27_3-release-notes、macos-release-notes/macos-27_3-release-notes。ActivityKit:Last-Modified: Thu, 08 Oct 2026 15:31:35 GMT,平台為 iOS、iPadOS 與 Mac Catalyst。 ↩↩↩↩↩↩↩↩↩↩↩

  7. 2026 年 10 月 28 日的 SDK 與範本檢查(作者分析),於 Xcode 27.1(27A9275)內的 MacOSX27.0.sdk:NSControl.h 第 180 行為 NSControlEventTrackingEndedOutside = 1 << 7,Xcode 27.2 beta(27B5019j)內的 MacOSX27.2.sdk 中也有相同的一行;NSEvent.h 第 496 行為 @property (class, readonly, getter=isTouchSwipeNavigationEnabled) BOOL touchSwipeNavigationEnabled API_AVAILABLE(macos(27));;NSTextSelectionManager.h 第 59 行宣告 gesturesForFailureRequirements;NSGestureRecognizer.h 第 116 與 149 行宣告 shouldRequireFailureOfGestureRecognizer:。專案範本以 plistlib 讀取,於 Xcode 27.1(27A9275)與 Xcode 27.2 beta(27B5019j)的 Platforms/MacOSX.platform/Developer/Library/Xcode/Templates/Project Templates/macOS/Application/ 之下:Cocoa App Base.xctemplate/TemplateInfo.plist 在 Options[0] (appLifecycle) / Units / Cocoa / Targets[0] / SharedSettings 中持有字串值為 NO 的鍵 INFOPLIST_KEY_NSViewGestureRecognizerIsExclusive,其他地方都沒有;位於 Project Templates/macOS/Base/ 之下的 macOS App Lifecycle.xctemplate 定義了 appLifecycle 選項,沒有 Name,Type 為 popup,Default 為 SwiftUI,Values 為 SwiftUI、Cocoa;macOS Application UI.xctemplate 定義了 userInterface,Name 為 Interface:,Default 為 SwiftUI,Values 為 SwiftUI、Storyboard、XIB,而它的 Storyboard 與 XIB 單元,如同其中一個 SwiftUI 單元,帶有 appLifecycle 為 Cocoa 的 RequiredOptions;App.xctemplate 經由 Core Data App、macOS SwiftUI App、macOS Application UI 與 Cocoa App Native Base 衍生自 Cocoa App Base;Cocoa Document App.xctemplate 在它的 Cocoa 生命週期單元中寫入一個 Info.plist:NSViewGestureRecognizerIsExclusive 節點,其定義為 <key>NSViewGestureRecognizerIsExclusive</key><false/>;Game.xctemplate 把 appLifecycle 設為 Override Cocoa;Document App.xctemplate 同時衍生自 Cocoa Document App 與 Cocoa App Base(經由 Core Data Document App、macOS SwiftUI Document-Based App、macOS Application UI 與 Cocoa App Native Base)。在兩個 Xcode 中,只有這兩個範本檔案包含這個鍵。Xcode 的 INFOPLIST_KEY_ 建置設定會寫入產生的 Info.plist。 ↩↩↩↩↩↩↩↩↩↩

  8. Apple Developer Documentation,TN3212: Adopting gesture recognizers for Sidecar touch support,修訂記錄「2026-06-08 First published」,「Support third-party UI frameworks」一節,四個項目:「A tap emulates a mouse-down followed immediately by a mouse-up」、「A touch and immediate pan emulates trackpad scroll events, even if the UI doesn’t respond to them」、「A long press without movement emulates a rightMouseDown(with:) (optionally followed by rightMouseDragged(with:)) with a rightMouseUp(with:) on touch lift」,以及「A two-finger pinch or rotation simultaneously emulates both the trackpad magnify and rotate gestures」。2026 年 10 月 28 日抓取。 ↩↩↩

  9. Apple Developer,Tech Talks Bring Touch to your Mac app(13:19,Raleigh Ledet)與 Design for touch on Mac(16:52,Cas Lemmens),2026 年 10 月 28 日。引文取自英文字幕軌;括號中的時間是引文段落的第一個字幕時間碼:「drag with a small hold delay」(111468,11:31)、「there’s a gesture modernization skill that has an understanding of the features and changes I’ve talked about」(111468,12:16)、「Touch on macOS uses a touch target of 36 points」(111456,9:17)、「a minimum of 36 points on each axis」(111468,5:53)。議程頁面的程式碼清單「Use NSControl events to detect tracking」(2:24)為 [.trackingBegan, .trackingEntered]、[.trackingExited, .trackingEndedInside] 與 .trackingCancelled 註冊了 addTarget(_:action:for:)。 ↩↩↩↩↩

相關文章

開發者眼中的 Touch on Mac:36 點、手勢辨識器與 Sidecar

開發者視角的 Mac 觸控:觸控會轉成點按,手勢辨識器是取得直接觸控的唯一途徑,觸控目標 36 點,以 Sidecar 測試,以及 SDK 尚未補上的空缺。

21 分鐘閱讀

macOS 27 與 iPadOS 27 的選單項目圖片會消失

macOS 27 與 iPadOS 27 預設隱藏選單項目圖片,而消失的內容取決於您連結的 SDK。三種框架,三種不同的修正方式。

10 分鐘閱讀

Mac App Store 不讓視窗管理器存在,我還是把它上架了

App Sandbox 禁用了每一款視窗排列工具都需要的 API,Apple 要我在商店之外發行。941 Tiles 卻在商店裡上架了:搬動視窗的是 Apple 自己的 Shortcuts 引擎。

9 分鐘閱讀