Touch on Macのドキュメント:移行ガイド、ゲーム向けテクニカルノート、HIG
トークの後、AppleはTouch on Macについて何を公開したのでしょうか。 10月28日の夜までに、正午には404を返していたAppKitの記事「Adopting gesture recognizers in your app」が公開され、マウスを扱うあらゆる種類のコードに次の一手を示す4行の表を載せていました。2本目の記事「Handling AppKit gestures」は、排他ビュー、止まったレコグナイザに対する2秒のタイムアウト、そしてデバッグ用の2つのユーザーデフォルトを説明しています。新しいテクニカルノートTN3216はゲームと、エミュレーションされるマウスイベントの順序を扱っています。そしてHuman Interface Guidelinesの3ページに、Macでのタッチについて10月28日付けの変更履歴が加わりました。1234 ランディングページの「Documentation resources」のタイルはいまこの記事とファイルプロミスのサンプルに、「Design resources」のタイルはHIGの3ページにリンクしています。Xcode 27.3ベータのタイルは、いまも「Coming soon」のままです。5
TL;DR: 10月28日午後1時47分(PT)に公開した開発者向けの記事は、3本のTech Talkと6月のテクニカルノートをもとに書きました。その記事が必要としていたAppKitの記事は、正午には404を返していました。午後9時51分には、その記事が公開され、隣にさらに2つのドキュメントが並んでいました。移行の記事は、コードを4つの行に振り分けます。標準コントロールとレコグナイザは何もする必要がありません。mouseDown(with:)のオーバーライドとトラッキングループは1本指のエミュレーションで動き、レコグナイザに置き換えるべきです。マウスのメソッドをオーバーライドするコントロールのサブクラスはトラッキングループに戻ります。終わらない独自のレコグナイザは排他ビューを握ったままになります。1 3行目の背後にあるルールは、AppKitはメソッドがオーバーライドされているかどうかで判断し、「not on what the override does」(オーバーライドが何をするかでは判断しない)というものです。ですからsuperを呼んでも助けになりません。1 残すループの横にbuttonMaskを0にしたタッチ専用のNSPanGestureRecognizerを置く方法がドキュメント化され、マスクをそのままにした場合の結果も正確に書かれています。マウスのドラッグでレコグナイザが始まり、mouseDown(with:)は一度も呼ばれません。1 排他ビューは、Info.plistのキーがなければオンです。そしてXcodeのmacOS AppテンプレートがこのキーをNOとして書き込むのは、アプリのライフサイクルがAppKitのアプリデリゲートであるプロジェクトだけで、デフォルトのSwiftUIライフサイクルでは書き込みません。これは、このMacにあるXcode 27.1と27.2ベータのテンプレートを読んで確かめたことです。27 止まったレコグナイザは操作が終わってから2秒後にキャンセルされます。デバッグ中は、1つのユーザーデフォルトでオーバーライドの欠落時にクラッシュさせ、もう1つで無言のキャンセルをクラッシュに置き換えられます。2 ゲーム向けのテクニカルノートは、タップとドラッグが生むマウスイベントを正確に並べ、エンジンに差分を足し合わせないよう求め、NSIsTouchNativeを設定するまでは各タッチ面が追加のエミュレーションされたGCMouseとして現れると説明しています。3 HIGはタッチを「a secondary input mode」(副次的な入力モード)と呼び、「tap targets bigger, not the controls themselves」(コントロールではなくタップターゲットを大きく)と述べ、ホバーでしか現れなかったコントロールについて6つのルールを加えました。4 まだないのは、Xcode 27.3ベータと、3つのシステムジェスチャのスイッチです。後者は午後9時51分(PT)の時点で404を返します。56
何が、いつ公開されたか
以下の各項目には、私が確認した時刻を添えています。サーバー自身のスタンプが、日中にページが示していた内容と食い違うからです。当日の記録はライブ記事にあり、今回の公開分についても後から1行を加えています。
- 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時8分(PT)です(証跡は出典に記載)。1 Tech Talk「Bring Touch to your Mac app」のページは、いまもこの記事にリンクしていません。5
- 2本目の記事。 「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」の1行だけで、サーバーのスタンプは午後2時9分(PT)です。3
- HIGの3ページ。 「Designing for macOS」「Gestures」「Pointing devices」の末尾に、2026年10月28日付けの変更履歴の行が加わりました。正午に取得したコピーには新しい文章がなく、午後9時51分に取得したコピーにはあります。新しい「Gestures」ページのサーバーの更新ヘッダは10月27日5時11分(UTC)で、文章がなかった正午のコピーより前の時刻です。両方の事実を記し、公開の時刻は示しません。4
- ランディングページ。 午前11時49分には、「Tools and documentation」の下の3つのタイルがすべて「Coming soon」で、リンクはHTMLのコメントの中にありました。午後9時51分には、「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.」)がHIGの3ページとApple Design Resourcesにリンクしています。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
- ほかに動いたものはありません。 開発者ニュースのフィードの最新項目は、いまも午前10時00分55秒(PDT)の「Say hello to Touch on Mac」です。リリースのフィードの最新項目は10月26日のポイントリリースで、Xcode 27.3やmacOS 27.3のベータはありません。Tech Talksの一覧は同じ3本のトークのままです。ActivityKitのページは10月8日のスタンプで、いまもmacOSのプラットフォームを載せていません。56
午後の記事では、書かれたルールはトークと6月のテクニカルノートだと書きました。この記事は、書かれたルールが何を加えたかについてのものです。また、夜の確認で違う情報が見つかったところについては午後の記事を訂正しており、その一覧は最後にあります。
コードを振り分ける表
記事の「Prepare your app for a Mac with a touch display」の節は、入力処理、タッチでの挙動、次の一手という3つの列と4つの行からなる表です。Appleの挙動の列は引用し、ほかの2列は要約しました。ただし3行目の次の一手は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.」 | キャンセルを処理し、終了状態に到達させる。 |
表に続くテストは4行で、書かれたとおりに実行する価値があります。「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」(タップにコントロールが反応しボタンが期待どおり強調・作動する、ビューのドラッグとテキスト選択とスクロールが1本指で動く、ピンチと回転が対応箇所で動く、リサイズグリップやつまみのような小さなターゲットが指で押しやすい)。1 検索の一覧は、トークが勧めたmouseDown(with:)のオーバーライドの監査を5つのシンボルに広げています。「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.」1 最後の確認は、4行目が扱うものです。「confirm that your app stays responsive after a touch interaction ends, and that every gesture recognizer action method handles the NSGestureRecognizer.State.cancelled state.」(タッチ操作が終わった後もアプリが応答し続け、すべてのジェスチャレコグナイザのアクションメソッドがキャンセル状態を処理することを確かめる)。1
Overviewの2つの文は、トークが推測に任せていた問いに答えています。エミュレーションは1本指です。「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.」です。1
オーバーライドのルール
表の3行目の背後には、トークも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」とあるので、オーバーライドがsuper.mouseDown(with:)を呼んでいてもこのフォールバックは起き、「Overriding mouseDragged(with:) or mouseUp(with:) has the same effect.」です。1
置き換え先はトークにあったコントロールイベントのパターンで、記事のコードリストはトークのものと1点だけ違い、そこは真似する価値があります。トークの2:24のコードリストは、[.trackingExited, .trackingEndedInside]に1つのアクションを、.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」。抜け道はデータソースとデリゲートのドラッグ用メソッドで、テーブルならtableView(_:pasteboardWriterForRow:)です。1
よくある2種類の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.」(ほとんどはクリックで項目を選択済みにするか、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.」(主ボタンにも反応し、マウスのドラッグでレコグナイザが始まってmouseDown(with:)が呼ばれず、ループは走らない。動かないクリックはループに届く)となります。1 つまりマスクはおまけではありません。設定しなければ、ループは何も言わずにドラッグを受け取らなくなります。allowedTouchTypesの行は念には念を入れたものです。「Direct touches are already the default for allowedTouchTypes, and setting it to .direct states that intent explicitly.」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
2つのデリゲートメソッドで絞り込む
トークと比べてもっとも新しい節は「Filter gesture recognizer candidates」です。レコグナイザはレスポンダチェーンではなくヒットテストで集められます。「Gesture recognizers don’t use the responder chain.」1 AppKitがそれらを集めるとき、「not every recognizer it finds is actually applicable to the interaction」(見つけたレコグナイザのすべてがその操作に当てはまるわけではない)ので、アプリは2つのデリゲートメソッドで当てはまらないものを外します。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.」1 イベントのメソッドだけで絞り込むと、直接タッチはそのデリゲートでは絞り込まれないまま残ります。
2つの例は、そのまま写されるだろうと私が予想するパターンです。リサイズグリップでは、リサイズのレコグナイザをビュー全体に付け、両方のデリゲートメソッドで位置がグリップの枠の中にあるかどうかを返します。trueを返すのは「only to give the gesture recognizer a chance to attempt recognition, not to guarantee it succeeds」(認識を試みる機会を与えるだけで、成功を保証するものではない)とされています。1 クリップの並ぶタイムラインでは、タイムラインに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
生き残ったレコグナイザの間の優先順位は失敗要件で決まります。記事は2本目の記事の警告「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.」(レスポンダチェーンが受け取るのはトラックパッドの間接タッチだけで、タッチディスプレイの直接タッチは受け取らない)。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について挙げられている例は1つだけで、「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.」(キーボードとスクロールホイールのイベントはどの入力元でもレスポンダチェーンで届き、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
独自のレコグナイザ:契約、タイムアウト、2つのデフォルト
独自のNSGestureRecognizerサブクラスについての記事のガイドラインは、7項目の箇条書きからなる契約です。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以降でビルドすると、AppKitはNSZeroPointを返してコンソールにエラーを記録する)からです。1 続くDragTrackingRecognizerの例は骨組みです。マウスダウン、ドラッグ、アップの各オーバーライドの本体は、非公開のtrack(_:event:state:)メソッドを1つ呼ぶようにというコメントで、キャンセルのオーバーライドの本体は.cancelledを設定するようにというコメントです。アクションメソッドは、キャッシュした位置とイベントを読み戻します。1
契約を破った結果を数字で示しているのは2本目の記事です。「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.」(マウスボタンを離す、指を上げるなどしてユーザーが操作をやめてから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.」(止まったレコグナイザ1つがアプリをいつまでも塞がないよう、システムはそれを自動的にcancelled状態へ遷移させ、ジェスチャの収集を通常どおり再開させる)。2 これが表の4行目の背後にある仕組みです。つまり出荷済みのアプリで壊れたレコグナイザがあると、ユーザーが操作をやめてから2秒後に自動でキャンセルされるまで、排他階層の外でのジェスチャの収集が塞がれ、アプリのほかの部分は応答しないように見えます。そしてキャンセルを処理する責任は、依然としてアクションのターゲットにあります。
開発中にこれらの問題をスタックトレース付きのクラッシュにする、2つのユーザーデフォルトがあります。1つはオーバーライドの欠落用、もう1つは無言のキャンセルの代わりです。「Set the NSGestureRecognizerCrashOnMissingOverrides key in UserDefaults to YES」とするか、起動引数として-NSGestureRecognizerCrashOnMissingOverrides YESを渡すと、オーバーライドすべきイベントのメソッドが欠けているときにクラッシュします。またNSCrashOnStuckGestureTimeoutを「to a small number of seconds, such as 2」(2のような小さな秒数に)設定するか、-NSCrashOnStuckGestureTimeout 2を渡すと、止まったレコグナイザをキャンセルする代わりにクラッシュします。2 1つ目がすべてを捕まえるわけではありません。これは「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」(出荷前にこの2つをコメントアウトするか削除する)、そして「Changes to these user defaults or launch arguments take effect only after you rebuild or relaunch your app.」(これらのユーザーデフォルトや起動引数の変更は、再ビルドか再起動の後でしか反映されない)。2 どちらのデフォルトも、トークにも6月のテクニカルノートにも出てきません。
同じ記事は、成功ではない2つの終了状態の違いも説明しています。.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éの例以外に、システムがキャンセルする原因を1つ挙げています。「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
同じページからさらに小さな事実を3つ。レコグナイザはレスポンダのイベントを遅らせることができます。「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.」(delaysPrimaryMouseButtonEventsを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月のテクニカルノートから排他性を説明しました。2本の記事は、デフォルト、仕組み、そして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に複数の指で同時に触れても、マウスポインタが1つしかない状態を模す)。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.」(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にする。異なるビューでの同時認識を意図して必要とする場合だけにする)。6 ビュー単位でオプトアウトするシステムの例はカラーパネルです。「AppKit uses this same technique in the standard Color Panel」とあり、そこでは複数のスライダーが同時に動きます。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アプリのプロジェクトにこのキーをNOで加えるので、YESに変えない限り排他ビューは使われない)。1 これがどのプロジェクトに当てはまるのかを知るため、このMacにあるプロジェクトテンプレートを読みました(筆者による分析)。Xcode 27.1(27A9275)とXcode 27.2ベータ(27B5019j)では、macOS Appテンプレートは「Cocoa App Base」というユニットを継承しており、そのアプリライフサイクルのオプションにはSwiftUIとCocoaの2つの値があり、デフォルトはSwiftUIです。値NOを持つキーINFOPLIST_KEY_NSViewGestureRecognizerIsExclusiveはCocoaのユニットの中にだけあります。ですからXcodeがこれを書き込むのは(生成されるInfo.plistに入るビルド設定として)、プロジェクトがAppKitのアプリデリゲートのライフサイクルを使う場合で、SwiftUIのライフサイクルを使う場合ではありません。7 ライフサイクルのオプションには、テンプレートの中で表示名がありません。表示名があるのはInterface:のオプション(SwiftUI、Storyboard、XIB、デフォルトはSwiftUI)で、StoryboardとXIBのユニットはAppKitのライフサイクルを必須としています。そのため、StoryboardやXIBのプロジェクトにはキーが入り、インターフェースがSwiftUIのプロジェクトには、ライフサイクルがAppKitのアプリデリゲートである場合にだけ入ります。7 Document Appテンプレートも同じベースから派生しているので、同じ条件のもとでそのビルド設定を継承し、さらにキーを値falseのInfo.plistの項目としても書き込みます。macOS GameテンプレートはCocoaのライフサイクルを強制するので、新しいmacOSのゲームは非排他で始まります。7 このMacには27.1より古いXcodeがないため、テンプレートがいつこのキーを持つようになったのかは言えません。実際的な読み方はこうです。これらのテンプレートから作ったAppKitのプロジェクトは非排他で始まり、SwiftUIのプロジェクトと、Info.plistにキーのない既存のアプリはすべて排他です。2本目の記事の移行についての注意も、そこから導かれます。アプリがキーを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
1本目の記事は、排他性とスクロールに関わる3つの設定を解きほぐし、「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.」(ビューの設定はそのビューとサブビューに適用され、アプリ全体のキーより優先される)。1 isCancellableByScrollGestureはレコグナイザごとの独立した設定で、「It lets a scroll gesture cancel that gesture recognizer even if it has already begun.」(すでに始まっていても、スクロールのジェスチャがそのレコグナイザをキャンセルできる)。1 scrollGestureForRelationshipsは、スクロールビュー自身のレコグナイザと競合するレコグナイザのためのものです。1 記事が注意すべきだとする場面は、スクロールビューの中のボタンです。そのレコグナイザでisCancellableByScrollGestureをtrueにすれば、ボタンの上で始めたドラッグはクリックではなくスクロールになります。1
テキストエンジンにはもう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
5項目のエミュレーション
午後の記事では、AppKit以外のUIフレームワーク向けの、6月のテクニカルノートにある4つのエミュレーションのルールを引用しました。記事の一覧は5項目で、新しいのはドラッグです。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.」(2本指のピンチや回転は、トラックパッドの拡大と回転のジェスチャになる)
3項目めは、トークの「drag with a small hold delay」(11:31)を文章にしたものです。すぐに動くタッチはスクロールし、先に押さえるタッチはドラッグします。9 6月の一覧にはタップ、すぐのパン、長押し、2本指のピンチまたは回転があり、ドラッグの項目はありませんでした。8 上限は2本指です。「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本か2本の指の操作が対象で、ピンチや回転の最中に別の指が触れても無視し、最初の2本を追い続ける)。1 3本以上の指にはレコグナイザが必要で、フレームワークが自分でタッチを扱うようになれば、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を背後に持つ単一のビューのアプリのように、ゲームによくある構成に焦点を当てる)。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はゲームのカテゴリのアプリにゲーム専用のエミュレーションを適用し、サードパーティの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」(マウスは位置から位置へ連続して動くが、プレイヤーは離れた2か所で続けてタップやドラッグを始められる)。そのため差分を足し合わせるゲームは位置がずれていきます。そこで「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:)、「repeatedly while the touch continues」(タッチが続く間繰り返し)のmouseDragged(with:)、そしてmouseUp(with:)です。3 そして「Important」の囲みにルールが1つあります。「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のアプリよりもゲームに対して多くのことをします。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のエミュレーションはアプリのカテゴリやサードパーティのUIフレームワークの有無を無視して汎用のものにとどまり、GCMouseのエミュレーションを無効にする)となり、タッチ面はもうGCMouseとして現れず、「Your game receives touch through gesture recognizers instead of emulated mouse events.」(ゲームはエミュレーションされたマウスイベントではなくジェスチャレコグナイザでタッチを受け取る)。3 1つだけ残るものがあります。「AppKit still emulates mouse events for any NSView that handles them, for example by overriding mouseDown:.」(mouseDown:のオーバーライドなどでマウスイベントを扱うNSViewには、AppKitは引き続きマウスイベントをエミュレーションする)。3 キー自身のページは、ほとんどのAppKitアプリには不要で、「primarily intended for apps that embed a non-AppKit rendering or input framework and implement their own touch-handling pipeline」(主に、AppKit以外の描画・入力フレームワークを組み込み、独自のタッチ処理を実装するアプリ向け)だと述べています。ゲームエンジンはまさにその場合です。6
実装の節は、SwiftとObjective-Cでのデリゲートのパターンです。物理マウスを無視するようbuttonMaskを0にしたパンとタップのレコグナイザ、拡大のレコグナイザ、パンとピンチの組に対してだけtrueを返すshouldRecognizeSimultaneouslyWith、そしてタップに両方を待たせるshouldRequireFailureOfです。3 注記は1本目の記事が述べるデフォルトを裏づけています。「The gesture recognizers must include direct in allowedTouchTypes. On macOS 27 and later, this is the default.」3 振り分けの助言はエンジンを前提にしたものです。「Install the gesture recognizers once in your app and leave them enabled」(レコグナイザはアプリで一度だけ設置し、有効のままにする)、コールバックをエンジンの入力イベントに変換し、「Let the active input handler decide if and how to react to different gestures」(さまざまなジェスチャに反応するかどうか、どう反応するかは、その時点で有効な入力ハンドラに決めさせる)。パンのハンドラは、バッキングスケールファクタで点をピクセルに変換し、原点が左上のエンジンのためにy軸を反転します。3 標準コントロールは切り替えの後も動き続けます。「as long as you don’t attach your own direct-touch gesture recognizers to them.」(自分の直接タッチ用レコグナイザをそれらに付けない限り)。3
締めくくりのチェックリストは2つに分かれています。エミュレーションのままのゲームでは、各イベントから位置を読み、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
10月28日付けのHIG
午後の記事では、ランディングページがリンクする予定だったHIGの4ページには、Macのディスプレイでのタッチについてのガイダンスがないと書きました。いまはそのうち3ページにあり、それぞれに2026年10月28日付けの変更履歴の行があります。Live Activitiesのページは変わっていません。4
Designing for macOS(「Added guidance for touch input.」)は、プラットフォームの説明の3行を書き換え、ベストプラクティスを1つ加えました。人間工学については「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」を挙げるようになりました。新しいベストプラクティスは、デザインのトーク全体を1文にしたものです。「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 続いて、それぞれ太字の見出しで始まる6つのルールがあります。
- 「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との対比が要点です。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のアプリ、またはトラックパッドのジェスチャです。そして「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ポイントという数字を示していません。その数字があるのは2本のトークです(デザインのトークの9:17の「Touch on macOS uses a touch target of 36 points」、そしてAppKitのトークのヒット矩形を広げる話の「a minimum of 36 points on each axis」(5:53))。9 ページには「macOS trackpad and touch display gestures」という表が加わり、それぞれの操作をトラックパッドとタッチでの形と組にしています。選択はプライマリクリックかタップ。開くはダブルクリックかダブルタップ。スクロールはトラックパッドでは2本指、ディスプレイでは1本指。ズームと回転はどちらでも同じピンチと2本指のひねり。コンテキストメニューはセカンダリクリックか長押し。ドラッグはクリックしてドラッグ、または「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のアプリには、ポインタが上に来たときだけ現れるコントロールがある。タッチ対応の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
これら6つのルールは、2本のトークのホバーについてのガイダンスを文章にしたもので、トークが示さなかった時間と例外が加わっています。表示したコントロールが隠れるまでの数秒、作動させるための2回目のタップ、そして表示されたまま残る場合としてのサイドバーとタブの閉じるボタンです。同じ改訂では、ページのiPadOSのポインタについての記述がLiquid Glass向けに書き直され、本文のデフォルトのポインタの形が円から矢印に変わり、カーソルの表のAppKitだけの列がAppKitとSwiftUIのシンボルに置き換わり、ポインタのマグネティズムの節が削除されました。どれもMacのディスプレイの話ではありません。4
まだないもの
10月28日午後9時51分(PT)に、Appleのドキュメントのエンドポイント、ランディングページ、開発者ニュースとリリースのフィード、Tech Talksの一覧と照らし合わせて確認しました。56
- Xcode 27.3ベータ。ランディングページのタイルはいまも「Coming soon」で、リリースのフィードには10月26日のポイントリリース(macOS 27.2を含む)より新しいものがなく、Xcode 27.3とmacOS 27.3のリリースノートのページは404を返します。最新のXcodeは引き続き27.2 RCで、そのSDKは私は調べていません。午後の記事のデプロイメントターゲットについての注意は引き続き当てはまり、デプロイメントターゲットの背景はXcode 27のリリースの記事にあります。
- AppKitのトークが実演した3つのシステムジェスチャのスイッチ、
NSWindow.disablesDirectTouchSystemGestures、UIViewController.prefersDirectTouchSystemGesturesDisabled、SwiftUIのdirectTouchSystemGesturesDisabled()。3つとも404を返し、新しい2本の記事のどちらも触れていません。記事は、システムジェスチャがレコグナイザをキャンセルする仕組みは説明していますが、システムジェスチャをオフにする方法は説明していません。 - macOSで使えるActivityKit。フレームワークのページは10月8日のスタンプで、macOSのプラットフォームを載せていません。Live Activitiesのトークにあった
AlertConfiguration.announcement(_:)は404を返します。午後の記事のLive Activitiesの節は書いたとおりのままで、ステートマシンの記事も引き続きiOSでの実装にとどまります。 - AppKitのトークが12:16で触れた「gesture modernization skill」。それを名指しするAppleのページはなく、ランディングページの
TK skillのコメントも残っています。59 - トークのページから記事へのリンク、そしてMacのタッチについてのシミュレータやDevice Hubの経路に関するAppleの言及。
- SwiftUIのドキュメント更新ページの日付付きの項目。サーバーのスタンプは10月28日午後2時58分(PT)ですが、最新の節はいまも2026年9月で、ジェスチャの項目は2026年6月の
GestureInputKindsの行です。何が変わったにせよ、新しい日付の節ではありませんでした。6
開発者向け記事の訂正
午後の記事は正午の確認の時点では正確でしたが、10月28日午後9時51分(PT)の確認では、その記述のうち5つが成り立たなくなっていました。その記事には、この記事と同時に公開した更新ブロックがあってここにリンクしており、記述はその場で訂正しています。以下がその一覧です。
- ランディングページの3つの「Coming soon」のタイルは、いまは1つです。ドキュメントのリソースとデザインのリソースは公開され、Xcode 27.3ベータはまだです。5
- 「Adopting gesture recognizers in your app」は、もう404を返しません。公開されており、移行のガイダンスはこのドキュメントから読むべきです。1
- HIGのページは、Macのディスプレイでのタッチについてもう沈黙していません。4ページのうち3ページに10月28日の変更履歴の行があり、Live Activitiesのページにはありません。4
- エミュレーションの一覧は、6月のテクニカルノートの4項目ではなく5項目です。加わったのは、押さえてからドラッグするルールです。18
- 排他ビューがデフォルトでオンになるのは、Info.plistにキーのないアプリだけです。Xcode 27.1と27.2ベータのXcodeのmacOS AppとDocument Appのテンプレートは、アプリのライフサイクルがAppKitのアプリデリゲートであるプロジェクトに対して、キーを
NOとして書き込みます。127
午後の記事がSDK、ドキュメント化されていない3つのスイッチ、ActivityKitについて書いたことは、その後に公開されたものによって何も変わっていません。
要点
AppKit開発者へ: Sidecarで記事の4行のテストを実行し、それから記事が挙げる5つのシンボルを検索しましょう。コントロールのサブクラスでmouseDown(with:)、mouseDragged(with:)、mouseUp(with:)のいずれかをオーバーライドすると、superを呼ぶかどうかにかかわらず、そのコントロールはトラッキングループに戻ります。対処は、非表示のセットに.trackingEndedOutsideを含めた、NSControl.Eventsでのターゲットとアクションの組です。1
残すループには: タッチ専用のレコグナイザにはbuttonMaskの0が必要です。そうしないとマウスのドラッグでレコグナイザが始まり、mouseDown(with:)は呼ばれません。allowedTouchTypes = .directはすでにデフォルトです。1
独自のレコグナイザには: 終了状態に到達させ、reset()とlocation(in:)をオーバーライドし、-NSGestureRecognizerCrashOnMissingOverrides YESと-NSCrashOnStuckGestureTimeout 2でデバッグして、出荷前に外しましょう。本番では、止まったレコグナイザはユーザーが操作をやめてから2秒後にキャンセルされます。12
排他性については: キーのないアプリは排他です。XcodeのmacOS AppテンプレートからAppKitのアプリデリゲートのライフサイクルで作ったプロジェクト(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
私のアプリで排他ビューはオンですか。
Info.plistにNSViewGestureRecognizerIsExclusiveキーがなければ、オンです。「If your app’s information property list doesn’t include the NSViewGestureRecognizerIsExclusive key, the system treats it as YES.」2 Xcode 27.1または27.2ベータのmacOS AppテンプレートからAppKitのアプリデリゲートのライフサイクルで作ったプロジェクト(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」(ユーザーが操作をやめてから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.」(マウスのドラッグでレコグナイザが始まり、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ポイントという数字は2本のトークにあり、HIGにはありません。49
この記事の事実はどこから来ていますか。
Appleの2本のAppKitの記事、TN3216、HIGの3ページ、ランディングページです。いずれも2026年10月28日午後9時51分(PT)に取得し、記事の公開状況、HIGのページ、ランディングページは、同じ日の正午と午前11時49分の確認と比べました。さらに、名前を挙げたすべてのシンボルについてのAppleのドキュメントJSON、比較のための6月のテクニカルノート、トークの引用のためのTech Talkの字幕トラック、そして私のMacにあるXcode 27.1とXcode 27.2ベータの中のヘッダとプロジェクトテンプレートです。最後のものは筆者による分析です。報道は情報源にしていません。
出典
-
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」に次ぐ2番目に、また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」(表、4行のテスト、検索の一覧)、「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」のコードリストを4行に縮めたものです。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
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」。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
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」。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
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日で終わっています。最初の3ページの正午のコピーには、引用した文章はどれも含まれていません。新しいGesturesのJSONのサーバーの
Last-ModifiedヘッダはTue, 27 Oct 2026 05:11:02 GMTで、午後9時59分の最初のリクエストではX-Cache: hit-staleでした。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
Apple Developer、Get ready for Touch on Mac。2026年10月28日午前11時49分(PT)に取得(3つのタイルが「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)に取得。 ↩↩↩↩↩↩↩↩↩ -
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 isYES.」)、NSIsTouchNative、Mouse, Keyboard, and Trackpad(レスポンダチェーンとタッチについての2つの文)、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。 ↩↩↩↩↩↩↩↩↩↩↩ -
2026年10月28日のSDKとテンプレートの確認(筆者による分析)。Xcode 27.1(27A9275)内の
MacOSX27.0.sdk:NSControl.hの180行目はNSControlEventTrackingEndedOutside = 1 << 7で、Xcode 27.2ベータ(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:を宣言しています。プロジェクトテンプレートは、Xcode 27.1(27A9275)とXcode 27.2ベータ(27B5019j)のPlatforms/MacOSX.platform/Developer/Library/Xcode/Templates/Project Templates/macOS/Application/の下にあるものをplistlibで読みました。Cocoa App Base.xctemplate/TemplateInfo.plistは、Options[0] (appLifecycle) / Units / Cocoa / Targets[0] / SharedSettingsにキーINFOPLIST_KEY_NSViewGestureRecognizerIsExclusiveを文字列値NOで持ち、ほかの場所にはありません。Project Templates/macOS/Base/の下のmacOS App Lifecycle.xctemplateは、appLifecycleオプションをNameなし、Typepopup、DefaultSwiftUI、ValuesSwiftUI、Cocoaで定義しています。macOS Application UI.xctemplateはuserInterfaceをNameInterface:、DefaultSwiftUI、ValuesSwiftUI、Storyboard、XIBで定義し、そのStoryboardとXIBのユニットは、SwiftUIのユニットの1つと同様に、appLifecycleCocoaを指定したRequiredOptionsを持ちます。App.xctemplateは、Core Data App、macOS SwiftUI App、macOS Application UI、Cocoa App Native Baseを経てCocoa App Baseから派生しています。Cocoa Document App.xctemplateは、Cocoaのライフサイクルのユニットの中で、定義が<key>NSViewGestureRecognizerIsExclusive</key><false/>であるInfo.plist:NSViewGestureRecognizerIsExclusiveノードを書き込みます。Game.xctemplateはappLifecycleをOverrideCocoaとしています。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でも、このキーを含むテンプレートファイルはこの2つだけです。XcodeのINFOPLIST_KEY_のビルド設定は、生成されるInfo.plistに書き込まれます。 ↩↩↩↩↩↩↩↩↩↩ -
Apple Developer Documentation、TN3212: Adopting gesture recognizers for Sidecar touch support、改訂履歴「2026-06-08 First published」、節「Support third-party UI frameworks」の4項目:「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日に取得。 ↩↩↩
-
Apple Developer、Tech Talk 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:)を登録しています。 ↩↩↩↩↩