-
Bring Touch to your Mac app
Discover how to add touch capabilities to your Mac app. Explore how to transition from classic mouse tracking APIs to modern gesture recognizers and touch-native controls. Find out how to optimize hit targets, implement multi-touch interactions, and manage system gestures to deliver an incredible touch experience. To get the most out of this session, watch Design for Touch on Mac.
Chapters
- 0:00 - Introduction
- 0:30 - Touch across frameworks
- 1:37 - Use control events
- 3:05 - Adopt gesture recognizers
- 5:14 - Expand hit targets
- 6:24 - Customize touch scrolling
- 6:57 - Manage gesture exclusivity
- 8:17 - Disable system gestures
- 9:39 - Additional touch tips
- 11:56 - Next steps
Resources
Related Videos
Tech Talks
-
Search this video…
-
-
2:08 - Custom button that overrides mouseDown
// Custom button to detect start and end of tracking class IgnitionButton: NSButton { var delegate: IgnitionButtonDelegate? override func mouseDown(with event: NSEvent) { delegate?.buttonDidStartTracking() super.mouseDown(with: event) delegate?.buttonDidEndTracking() } } -
2:24 - Use NSControl events to detect tracking
// Use NSControl events to detect start and end of tracking 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 ) -
3:13 - Mouse tracking loop
let eventMask: NSEvent.EventTypeMask = [.leftMouseDragged, .leftMouseUp] while true { let nextEvent = NSApp.nextEvent( matching: eventMask, until: .distantFuture, inMode: .eventTracking, dequeue: true) if let nextEvent, nextEvent.type == .leftMouseUp { break } } -
4:11 - Refactored tracking functions
// Refactored tracking functions func startTracking(at viewLocation: NSPoint) { /* ... */ } func continueTracking(at viewLocation: NSPoint) { /* ... */ } func endTracking(at viewLocation: NSPoint) { /* ... */ } func cancelTracking() { /* ... */ } -
4:24 - Add a touch-only pan gesture recognizer
// Add touch-only pan gesture recognizer let touchRecognizer = NSPanGestureRecognizer( target: self, action: #selector(touchPan) ) touchRecognizer.buttonMask = 0x0 touchRecognizer.allowedTouchTypes = .direct wiringPanelView.addGestureRecognizer(touchRecognizer) -
4:52 - Pan gesture recognizer action
// Pan gesture recognizer action function @objc func touchPan(_ recognizer: NSPanGestureRecognizer) { switch recognizer.state { case .began: startTracking(at: recognizer.location(in: wiringPanelView)) case .changed: continueTracking(at: recognizer.location(in: wiringPanelView)) case .ended: endTracking(at: recognizer.location(in: wiringPanelView)) case .cancelled: cancelTracking() default: break } } -
5:37 - Expand hit targets for touch
// Refactored tracking functions with expanded hit targets func startTracking(at viewLocation: NSPoint, expandHitRect: Bool = false) { for node in nodes { if point(viewLocation, in: node.frame, expandForTouch: expandHitRect) { trackingNode = node trackingLocation = viewLocation return } } } func point(_ point: NSPoint, in rect: NSRect, expandForTouch: Bool = false) -> Bool { if expandForTouch { // Expand to minimum size of 36 points on each axis } else { // Nothing to do for mouse } return NSPointInRect(point, rect) } @objc func touchPan(_ recognizer: NSPanGestureRecognizer) { switch recognizer.state { case .began: startTracking(at: recognizer.location(in: wiringPanelView), expandHitRect: true) // ... default: break } } -
6:40 - Require two touches for scrolling
// Modify touch scrolling properties decoratingView.enclosingScrollView?.minimumNumberOfTouchesForScrolling = 2 -
7:48 - Allow simultaneous slider interaction
// Simultaneous sliders stoveControlsView.exclusiveGestureBehavior = .notExclusive -
8:25 - Disable system gestures
// Disabling system gestures stoveControlsView.window?.disablesDirectTouchSystemGestures = true // Mac Catalyst API extension UIViewController { var prefersDirectTouchSystemGesturesDisabled: Bool { get } } // SwiftUI API View.directTouchSystemGesturesDisabled() -
11:47 - Declare touch-native support in Info.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <!-- ... --> <key>NSIsTouchNative</key> <true/> <!-- ... --> </dict> </plist>
-