On October 28 Apple announced the MacBook Pro with PROLED XDR Display, the first Mac with a touchscreen, running macOS 27 Golden Gate. The developer line in the announcement is that many existing apps work with Touch right away, because macOS automatically translates touches into clicks. For buttons, tables and SwiftUI gestures that holds. For a custom view with a tracking loop inside mouseDown(with:) it is half true. That view gets an emulated mouse: a click, a scroll, a right click on long press. It never sees the finger itself, and it has no way to back out when the system takes the gesture over.
Apple published three Tech Talks and a technote, TN3212. This article is what they mean for your code if you are working on macOS touchscreen support, and where to start. One clarification so the two announcements stay apart: the 14-inch MacBook Pro with M6 from October 27 has no touch. Touch belongs to the 14- and 16-inch PROLED XDR models with M5 Pro or M5 Max, pre-orders from October 28, shipping from November 6.
MacBook Pro touchscreen: what works without changes#
Apple calls buttons, controls, tables, collections, split views, tabs and scrolling "touch native": AppKit builds them on gesture recognizers, so a finger behaves like a click. Existing SwiftUI and Mac Catalyst gestures "just work," per the talk.
Everything else falls back to mouse emulation. TN3212 describes it for third-party UI frameworks and hand-written mouseDown(with:) code:
| Touch | What your app receives |
|---|---|
| Tap | mouseDown, then mouseUp |
| Touch with immediate pan | trackpad scroll events |
| Long press without movement | rightMouseDown, then rightMouseDragged if it moves, rightMouseUp on lift |
| Two-finger pinch or rotate | magnify and rotate at the same time |
The talk adds that one finger scrolls with momentum and a drag starts after a short hold. One design fact matters too: macOS on a touch machine does not look different. Window, text and control sizes are unchanged, and touch stays a secondary input.
Catalyst apps tell mouse from touch through the old UITouch.touchType and UIGestureRecognizer.allowedTouchTypes. macOS 27 also fixes one Catalyst gap: views with both UIDragInteraction and UIContextMenuInteraction can now trigger both by touch.
Direct touch arrives only through gesture recognizers#
The line from AppKit engineer Raleigh Ledet: "Gesture recognizers are the only way to get direct touches on macOS." TN3212 spells out the consequences. Local and global event monitors do not receive touches, they do not go through NSApp or sendEvent(_:), and they skip the responder chain. Keyboard and scroll-wheel events still use it.
Apple's own minimal example is a pan recognizer that accepts only a finger:
let touchRecognizer = NSPanGestureRecognizer(target: self, action: #selector(touchPan))
touchRecognizer.buttonMask = 0x0
touchRecognizer.allowedTouchTypes = .direct
wiringPanelView.addGestureRecognizer(touchRecognizer)allowedTouchTypes is an NSTouch.TouchTypeMask that has existed since macOS 10.12.2. What changed is that no other path gets a touch to your code.
Two traps from TN3212. Recognizers attach to views only, so to catch touches in a view controller, add the recognizer to its view. And AppKit collects candidates by walking up from the top-most hit-tested view, so a transparent overlapping view blocks recognizers, including the ones inside standard controls. Override hitTest(_:) and return nil in the overlay.
You can leave tracking loops in place and add touch-only recognizers beside them. Apple calls that "the fastest and least risky path for a dot release." For a bigger release it suggests splitting tracking into start, continue, end and cancel functions that both paths call.
Replace mouseDown tracking with NSControl events#
For your own buttons and sliders, Apple's route is to subscribe to NSControl.Events with addTarget(_:action:for:) instead of looping in mouseDown. The sample from the talk:
ignitionButton.addTarget(self, action: #selector(buttonDidStartTracking), for: [.trackingBegan, .trackingEntered])
ignitionButton.addTarget(self, action: #selector(buttonDidEndTracking), for: [.trackingExited, .trackingEndedInside])
ignitionButton.addTarget(self, action: #selector(buttonCancelledTracking), for: .trackingCancelled)The third line has no equivalent in the mouse world. trackingCancelled fires when a system gesture such as Exposé, or a scroll, interrupts tracking; revert unfinished changes there. The set also has valueChanged, primaryActionTriggered, menuActionTriggered, and the aggregates allTrackingEvents and allEvents.
Three warnings. If you subclass a standard control and override any left-mouse responder method, it drops back to a tracking loop. The same goes for NSTableView and NSCollectionView, where Apple points to the delegate methods. After migration, NSApplication.currentEvent no longer returns the event that triggered the action: read NSEvent.modifierFlags and NSEvent.pressedMouseButtons instead, and note that a touch does not show up in pressedMouseButtons. Finally, dates are slightly tangled: TN3212 lists trackingRepeated, valueChanged, primaryActionTriggered, menuActionTriggered and applicationReserved as new in macOS 27, while the docs date NSControl.Events itself to macOS 11. Check availability per member.
Let several gestures run at once#
By default only one view hierarchy can have active recognizers. That mimics a modal tracking loop, and scrolling is the exception. For panels where fingers move several things at once (Apple's design talk uses two sliders in Logic) you switch it off:
stoveControlsView.exclusiveGestureBehavior = .notExclusiveNSView.exclusiveGestureBehavior (macOS 27.0) takes .inherit by default, .exclusive or .notExclusive, and subviews inherit it. AppKit's standard color panel already lets several sliders move together. Three Info.plist keys, all macOS 27.0:
| Key | Type | Effect |
|---|---|---|
NSViewGestureRecognizerIsExclusive | Boolean, default YES | app-wide default; NO lets recognizers in several views be active together |
NSGestureRecognizerSuppressesMainMenuActions | Boolean | whether menu keyboard shortcuts are ignored during a non-exclusive gesture; exclusive gestures always suppress them |
NSIsTouchNative | Boolean | stops AppKit from injecting mouse-emulation recognizers for apps embedding a non-AppKit UI framework; Apple: "Most AppKit apps don't need this key" |
If no screen in your app needs two simultaneous gestures, leave all of this alone.
Hit targets: 36 pt in the talk, 44 pt in the HIG#
Designer Cas Lemmens states the number: "Touch on macOS uses a touch target of 36 points," which is the same physical size on the display as 44 points on iOS. Framework controls get an invisible 36 pt touch target and keep their visual size. The published HIG Buttons page, meanwhile, still asks for a hit region of at least 44×44 pt. So far the 36 pt figure lives only in the design Tech Talk and in a code comment in Apple's sample.
For custom controls, enlarge the touch hit area, not the control, and only on the touch path. In Apple's sample the expandHitRect logic runs from the touch recognizer, so the mouse stays precise. FormStyle rows default to 36 pt, inspectors keep medium controls, and small and mini controls still "fully support touch."
Hover has no touch equivalent. Tooltips need not work with touch, and hover-revealed scroll buttons stay hidden during touch. Anything essential that appears only on hover needs a second path, such as a context menu. Apple's AppKit option is a touch-only click recognizer that temporarily shows the element on tap.
Scroll views: two fingers for drawing canvases#
macOS 27 adds isTouchScrollingEnabled (default true), minimumNumberOfTouchesForScrolling (default 1), maximumNumberOfTouchesForScrolling and scrollGestureForRelationships to NSScrollView, the last for failure and exclusion relationships against scrolling. For a canvas where one finger draws and scrolling still has to work, Apple's sample is one line:
scrollView.minimumNumberOfTouchesForScrolling = 2There is also NSRefreshController for pull-to-refresh, attached through NSScrollView.refreshController with target/action, beginRefreshing() and endRefreshing(). A custom recognizer that should yield to scrolling can set isCancellableByScrollGesture, which is false by default.
In SwiftUI, name the input source#
GestureInputKinds (macOS 27.0) is an option set: .directTouch, .indirectTouch (trackpad multi-touch, Siri Remote), .pointer, .pencil and .all. Initializers with inputKinds exist on DragGesture, LongPressGesture, MagnifyGesture, RotateGesture, SpatialTapGesture, TapGesture and others. A canvas where a finger draws and the mouse selects:
let draw = DragGesture(minimumDistance: 0, coordinateSpace: .local, inputKinds: .directTouch)
let select = DragGesture(minimumDistance: 4, coordinateSpace: .local, inputKinds: .pointer)If you already have a custom NSGestureRecognizer, NSGestureRecognizerRepresentable wraps it for SwiftUI instead of a rewrite. Its methods are makeNSGestureRecognizer(context:), updateNSGestureRecognizer(_:context:), handleNSGestureRecognizerAction(_:context:) and makeCoordinator(converter:), attached with .gesture(_:). The docs date it to macOS 26.0 while WWDC26 calls it new, so "available" is the safer word.
System gestures take fingers first#
The design talk lists the display gestures: three fingers up for Mission Control, down for App Exposé, left and right to switch Spaces; thumb plus three fingers pinching in for apps, spreading for the desktop. A swipe down from the top edge shows the hidden menu bar, up from the bottom shows the Dock, left from the right edge opens Notification Center, down from the top-right edge opens Control Center. A swipe over the Dynamic Island summons Siri. Per the newsroom, double-tapping a window fills the screen and a flick tiles it.
The practical catch: moving three or more sliders in the same direction at once triggers a system gesture. For rare windows where that hurts, Apple's sample code shows three opt-outs: NSWindow.disablesDirectTouchSystemGestures, UIViewController.prefersDirectTouchSystemGesturesDisabled for Catalyst and View.directTouchSystemGesturesDisabled() for SwiftUI. They appear in sample code and have no documentation pages yet. Apple's advice: disable "only if the interaction capabilities they open up are critical for the window," ideally just for the critical moment.
Display and Dynamic Island, briefly#
ProMotion runs adaptively from 10 to 120 Hz. Brightness is up to 1000 nits sustained full-screen and 2000 nits peak (outdoor for SDR, HDR content for XDR). EDR APIs are unchanged, so NSScreen.maximumExtendedDynamicRangeColorComponentValue and CAMetalLayer.wantsExtendedDynamicRangeContent work as before. The docs show no new Dynamic Island geometry API for AppKit; safeAreaInsets and auxiliaryTopLeftArea are the existing ones. On burn-in, Apple says only that the display engine compensates for pixel wear and gives developers no guidance.
For Live Activities on Mac the Tech Talk says universal apps with existing activities work with no code change and native macOS apps can create them directly. They sit at the top edge on a Mac with a Dynamic Island and in the menu bar elsewhere. The ActivityKit documentation does not list macOS yet, and alertConfiguration: .announcement(...) from Apple's sample is missing from the AlertConfiguration members. Wait for the docs before building on it.
Test without the new Mac#
Apple: with the latest macOS beta and developer tools, all touch features are available on an iPad connected through Sidecar. On macOS 27 the touches from a Sidecar iPad on iPadOS 27 also arrive only through gesture recognizers.
Be careful with NSScreen.touchCapabilities (.multiTouch): with a touch-capable Sidecar display attached it returns true for all displays. Apple advises handling input regardless of source instead of branching on it.
Two debugging defaults: NSCrashOnStuckGestureTimeout crashes the app when a stuck exclusive gesture gets auto-cancelled a few seconds after input ends, and NSGestureRecognizerCrashOnMissingOverrides flags subclasses missing required overrides. One trap hits on rebuild: in apps built with the macOS 27 SDK, base NSGestureRecognizer.location(in:) returns NSZeroPoint and logs an error unless your subclass overrides it. Custom recognizers must reach .ended, .failed or .cancelled, otherwise they block others, so implement mouseCancelled(with:) and touchesCancelled(with:).
Every new symbol here is macOS 27.0, so wrap calls in #available(macOS 27, *) if you still support older systems.
First-week checklist#
- Rebuild with the macOS 27 SDK and look for
NSGestureRecognizersubclasses without alocation(in:)override. - Turn on
NSCrashOnStuckGestureTimeoutandNSGestureRecognizerCrashOnMissingOverridesin the debug scheme. - Connect an iPad via Sidecar and walk through the main screens by finger: tap, drag, long press, scroll.
- Find custom
mouseDowntracking and add a touch-only recognizer withallowedTouchTypes = .directbeside it. - Move your controls' tracking to
NSControl.Eventsand handletrackingCancelled. - Expand small custom hit areas to 36 pt on the touch path only.
- Find hover-only actions and give them a second route.
- Check windows with simultaneous gestures and decide whether you need
.notExclusive.
What Apple hasn't explained yet#
The Touch on Mac hub at developer.apple.com/touch-on-mac/ looks unfinished on launch day. "Documentation resources" and "Design resources" say "Coming soon," and so does the Xcode 27.3 beta. The article "Adopting gesture recognizers in your app" is commented out in the HTML and returns 404. The HIG has no touch-on-Mac page, and its Gestures page still says Mac users rely on keyboard, mouse and trackpad. The system-gesture opt-outs are undocumented, and Live Activities availability for native macOS apps is not reflected in the docs. The talk mentions a "gesture modernization skill" in the developer tools that is not yet findable. Questions go to the group labs on November 18 (9 to 10 a.m. PT) and November 19 (6 to 7 p.m. PT).
On Hacker News the argument is mostly about fingerprints, arm fatigue and the missing tablet mode, while mobile developers like testing iOS gestures right on the Mac screen. For your code it lands where Apple's design talk already is: touch is secondary, and every gesture also needs a trackpad route and a menu command.
If you are updating your interface for the new materials, compare notes with Liquid Glass adoption in SwiftUI. A similar story of adapting to new hardware is the iPhone Duo adaptation guide, and menu bar apps may want the macOS menu bar app in SwiftUI article. Primary sources: Touch on Mac, TN3212, and the Tech Talks Design for touch on Mac, Bring Touch to your Mac app and Bring Live Activities to Mac.



