Skip to content
Blog

Touch on Mac: The Hit-Target and Gesture Audit

Apple's first touchscreen MacBook Pro ships November 6, 2026. A practical audit of the AppKit and SwiftUI work, ordered by what breaks first.

Jorge Valbuena9 min read

Apple’s first touchscreen Mac goes on sale on Friday, November 6, 2026. Your existing Mac app will run on it on day one, and that is exactly the problem: macOS synthesises mouse events from a single finger, so the app demos fine and then fails in the places nobody demos.

What shipped, and what hasn’t

The hardware is the MacBook Pro with PROLED XDR Display (Apple’s capitalisation), announced October 28, 2026 with M5 Pro and M5 Max chips, up to 128GB of unified memory, up to 2000 nits peak HDR and SDR brightness and a 120Hz variable refresh rate. US pricing starts at $2,899 for the 14-inch M5 Pro and $3,399 for the 16-inch, rising to $4,499 and $4,799 for the M5 Max configurations. Pre-orders opened the day of the announcement, October 28; availability begins November 6. Apple describes the sensor as “built directly into the display panel — not layered on top of it.”

The software side is older than the news cycle suggests. Nearly every symbol in this audit is annotated macOS 27.0, and Apple published TN3212, Adopting gesture recognizers for Sidecar touch support, on June 8, 2026 — four months before the hardware. The OS releases are final too: macOS 27.2 (build 26B123) shipped October 26, 2026.

The tooling is where people are getting confused, including in some write-ups of the announcement. As of October 29, 2026, the most recent released Xcode is 27.1 (27A9275), from October 15, carrying the macOS 27 SDK. Xcode 27.2 exists only as a release candidate (27B48, October 19). And Apple’s own Touch on Mac hub lists “Xcode 27.3 beta” under Coming soon. So: build against the macOS 27 SDK in released Xcode 27.1, or the 27.2 SDK in the RC — and read the rest of this post knowing that the next SDK isn’t out.

Emulation is why the bugs are invisible

Apple states the trap plainly in Adopting gesture recognizers in your app: existing tracking loops and mouseDown overrides “can still run on a Mac with a touch display or a Sidecar-connected iPad, because 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.”

The harder constraint is that the emulation is a dead end, not a bridge. AppKit “doesn’t deliver direct touches (NSTouch) through the responder chain or through event monitors.” TN3212 is blunter: “Local and global event monitors don’t receive touch events. Touch events don’t go through NSApp or sendEvent(_:).” Gesture recognizers are the only route to multi-finger gestures or individual touches.

Apple ships a four-row triage table, which doubles as the audit, pre-sorted by severity:

Input handling Behavior with touch Next step
Standard controls and gesture recognizers “Works with touch, with no changes needed.” Test it
mouseDown(with:) overrides and tracking loops Single-finger emulation only Replace with gesture recognizers
NSButton/NSSlider subclasses overriding mouse methods “AppKit falls back to a tracking loop.” Replace overrides with target-action on tracking events
Custom NSGestureRecognizer subclasses A recognizer that never terminates “keeps its exclusive view, which can make your app appear unresponsive.” Handle cancellation

Start with grep, using Apple’s own list: nextEvent(matching:until:inMode:dequeue:), trackEvents(matching:timeout:mode:handler:), mouseDown(with:) and mouseDragged(with:) overrides, and uses of NSApplication.currentEvent inside control action methods.

The nastiest detail is in row three: “AppKit decides based on whether your subclass overrides the method, not on what the override does, so the fallback still applies if your override calls super.mouseDown(with:).” A one-line mouseDown override added years ago to fire an analytics event silently opts that control out of gesture-recognizer dispatch. Nothing in the UI hints at it.

What breaks the moment you build with Xcode 27

Two changes trigger on the SDK you compile against, not on the presence of a touchscreen. They land on every user running macOS 27 as soon as you ship a rebuild.

First, custom recognizers. Per TN3212: “In apps built with the macOS 27 SDK and Xcode 27, location(in:) on NSGestureRecognizer returns NSZeroPoint and logs an error if the receiver’s class doesn’t override the method.” If you have NSGestureRecognizer subclasses, that is the first thing to check.

Second, currentEvent. Because AppKit now routes NSControl interactions through gesture recognizers rather than the responder chain, “currentEvent no longer reliably returns the event that initiated an action.” Any modifier-key branch written this way is now a latent bug:

// Before — unreliable in macOS 27
if let event = NSApplication.shared.currentEvent,
   event.modifierFlags.contains(.shift) { /* ... */ }

// After
if NSEvent.modifierFlags.contains(.shift) { /* Handle a Shift-click. */ }

Apple notes the trade-off: these class properties “report the state at the time you query them, not at the time of the original event.” And for anything branching on mouse buttons: “A touch doesn’t register in pressedMouseButtons.”

Two more rules for custom recognizers: always reach a terminal state (.ended, .failed, .cancelled) — implement mouseCancelled(with:) and touchesCancelled(with:), and override reset() — and only mutate state inside event-processing methods, caching the originating event for the action method to read back.

Hit targets: 36 points, and where that number comes from

The figure circulating is at least 36 points on each axis. It is worth knowing exactly how well sourced that is. The first-party artifact is a code comment in the sample at 5:37 of Tech Talk 111468, Bring Touch to your Mac app: // Expand to minimum size of 36 points on each axis. The number appears in the two Tech Talks; it is not stated on the Human Interface Guidelines pages. Apple’s number lives in a video — plan accordingly when someone asks for a citation. The claim that 36 macOS points is physically equivalent to 44 iOS points is repeated widely but I could not find an Apple page asserting it, so treat it as unsourced.

The design rule matters more than the number: expand the target, not the control. Apple suggests making “small drag targets — like resize or grab handles — easier to hit with a finger by expanding their touchable area beyond what’s visible, without changing how they respond to a mouse.” Standard steppers, dials and sliders already expand in place on touch.

The mechanism is a recognizer on the parent view plus location filtering in the delegate — and you need both delegate methods, because Apple states that “direct touches don’t call” the NSEvent one:

func gestureRecognizer(_ g: NSGestureRecognizer,
                       shouldAttemptToRecognizeWith event: NSEvent) -> Bool {
    guard let view = g.view else { return false }
    return resizeGripFrame.contains(view.convert(event.locationInWindow, from: nil))
}

func gestureRecognizer(_ g: NSGestureRecognizer,
                       shouldReceive touch: NSTouch) -> Bool {
    guard let view = g.view else { return false }
    return resizeGripFrame.contains(touch.location(in: view))
}

Implement only the first and the audit has a touch-shaped hole in it.

Z-order now matters, and scroll views are opt-in

This is the bug class pointer code never exhibited. TN3212: “In the traditional AppKit responder-based model, mouse event handling was relatively insensitive to the z-ordering of sibling views.” Now AppKit “gathers all possible gesture recognizers at mouse-down or touch-began, using a strict walk of the view hierarchy from the hit-tested top-most view among siblings up to the window.”

Consequences, verbatim: “If your view is covered by another, often transparent, view, your gesture recognizer won’t be triggered,” and “you can’t place views on top of standard framework controls, because those controls now use gesture recognizers internally.” Grep for transparent overlays — click-catchers, drag shields, tooltip layers. Each is now a dead zone. Fix by rearranging siblings or overriding the occluding view’s hitTest(_:) to return nil.

The likeliest real-world regression is a control inside a scroll view. When someone touches down on a button and then drags to scroll, the scroll should win — which requires setting isCancellableByScrollGesture = true on the control’s recognizer. It defaults to false, so it is wrong by default in every custom control inside a scroll view. Apple is explicit that this is independent of exclusivity.

Speaking of which: the NSViewGestureRecognizerIsExclusive documentation says the default value is YES, while Xcode writes the key as NO into new macOS projects. Both are true, and the upshot is that an existing app and a fresh project behave differently out of the box. NSView.exclusiveGestureBehavior defaults to .inherit and takes precedence over the app-wide key.

Direct touch versus pointer — what the API actually distinguishes

SwiftUI’s GestureInputKinds is real and broadly available (iOS, iPadOS, Mac Catalyst, macOS, tvOS, visionOS, watchOS 27.0). It is an OptionSet with .directTouch, .indirectTouch, .pencil, .pointer and .all.

Be precise about what it separates. .pointer means “a person is pressing a mouse or a trackpad button” — it covers both, and does not distinguish a trackpad from a mouse. .indirectTouch is the trackpad-multitouch case. So “tell direct touch from trackpad” is only half true: you can tell a finger on the glass from a pointer click, and you can tell trackpad multitouch from either.

Confirmed entry points taking inputKinds: include View.onTapGesture(count:coordinateSpace:inputKinds:perform:), View.onLongPressGesture(...), DragGesture.init(minimumDistance:coordinateSpace:inputKinds:), MagnifyGesture.init(minimumScaleDelta:inputKinds:) and SpatialTapGesture.init(count:coordinateSpace:inputKinds:). Note the older onTapGesture(count:coordinateSpace:perform:) is now listed as deprecated, and that onTapGesture(count:inputKinds:perform:) — the signature most people would guess — does not exist; the coordinateSpace: overload is the one that takes input kinds.

Resist the urge to branch on hardware capability. NSScreen.touchCapabilities exists, but TN3212 warns that “in macOS 27, this call returns true for all displays when a touch-capable Sidecar display is connected, not just for the Sidecar display” — i.e. it over-reports during precisely the workflow Apple recommends for testing. Apple’s guidance: “handle events regardless of input source and decide on an alternate presentation based on the interaction type.” The sanctioned narrow use is offering a touch-specific affordance, such as capturing a signature.

One genuinely open question: .pencil is available on macOS 27.0, but Apple’s press release does not mention Apple Pencil support on the new MacBook Pro at all. The plausible reading is that .pencil on the Mac arrives via a Sidecar-connected iPad. I have no Apple source saying so. Don’t promise Pencil on this hardware.

What you cannot build yet, and how to scope the rest

Three symbols shown on Apple’s Tech Talk 111468 page at 8:25 — NSWindow.disablesDirectTouchSystemGestures, UIViewController.prefersDirectTouchSystemGesturesDisabled and SwiftUI’s View.directTouchSystemGesturesDisabled() — return HTTP 404 on developer.apple.com as of October 29, 2026, and independent header inspection of the macOS 27.0 SDK (Xcode 27.1) and the macOS 27.2 SDK in the Xcode 27.2 beta found all three absent from AppKit, UIKit and SwiftUI. The 27.2 RC hasn’t been inspected, and Apple has not named a version; the hub only says “Xcode 27.3 beta — coming soon.” Per-window system-gesture suppression is therefore not something to put on a schedule yet. Apple argues against rushing it anyway: “Only disable system gestures if the interaction capabilities they open up are critical for the window.”

Same story for the hub’s third Tech Talk. ActivityKit’s Activity documents availability for iOS and iPadOS 16.1 and nothing else, and the macOS Swift interface marks Activity, ActivityAttributes and every request/update overload @available(macOS, unavailable). “Bring Live Activities to Mac” is a preview, not a build target.

A caveat on method, since it cuts both ways: documentation availability metadata and 404s are evidence about Apple’s published docs, not proof about the SDK on your disk. Option-click the symbol in Xcode and check for yourself — and note that this material has high churn. Xcode 27.2 could go final, or 27.3 beta appear, any day.

You don’t need the hardware to start. Apple’s recommendation is Sidecar: connect an iPad, and “AppKit then routes touch on its screen the same way it does on a Mac with a touch display.” Then walk every window, sheet and popover with a finger and confirm Apple’s checklist — controls respond and highlight on a tap, one-finger drag/text-selection/scrolling work, pinch and rotate work where supported, resize grips and grab handles are easy to hit, and the app stays responsive after an interaction ends.

Finally, the scoping point. Apple’s own design guidance describes touch on a Mac as a secondary input mode used for “brief, intentional interactions rather than sustained work,” and says to keep layout and element sizes as they are. The lowest-risk migration path is Apple’s own: extract your tracking-loop body into shared functions and add a touch-only recognizer beside it with buttonMask = 0 and allowedTouchTypes = .direct, which Apple calls “often the fastest, lowest-risk way to add touch support to code with a tracking loop you’d rather not rewrite all at once.” That is an audit and a handful of targeted fixes with a November 6 deadline — not a redesign.