macOS 27 introduces NSTextSelectionManager for bringing native text-selection behaviors to any custom view, and reinforces best practices for graceful termination and state restoration so AppKit apps quit and relaunch seamlessly.
โข NSTextSelectionManager is a new API in macOS 27 that brings gesture-recognizer-backed text selection to any view
โข NSControlEvents pattern (familiar from UIKit) is now formally highlighted for AppKit controls, reducing need for mouseDown overrides
โข NSStatusItem expanded interface session API (expandedInterfaceSession) is reinforced for correct keyboard navigation life cycle management
โข preventsApplicationTerminationWhenModal guidance clarified โ developers should explicitly set to false for non-critical sheets
โข NSTextSelectionManager (new in macOS 27) enables bidirectional text selection, drag-and-drop, and toggling in any custom NSView without subclassing NSTextView
โข Proper use of NSWindowRestoration and encodeRestorableState ensures users return to exactly where they left off after system reboots or overnight updates
โข Adopting gesture-recognizer-based APIs and NSControlEvents future-proofs event handling across AppKit, SwiftUI, and Mac Catalyst
Demonstrates attaching NSTextSelectionManager to a custom NSView in macOS 27 to get full system text-selection behaviors, plus encoding and restoring that view's selection state across app launches.
import AppKitโ// Pre-macOS 27: manual mouseDown tracking loop for text-range selection+// MARK: - Custom view adopting NSTextSelectionDataSourceโclass LegacySelectableView: NSView {+class SelectableContentView: NSView, NSTextSelectionDataSource {โ var selectionStart: Int = 0โ var selectionEnd: Int = 0โ private var isTracking = false+ private var textSelectionManager: NSTextSelectionManager!โ override func mouseDown(with event: NSEvent) {โ let point = convert(event.locationInWindow, from: nil)โ selectionStart = characterIndex(at: point)โ selectionEnd = selectionStartโ isTracking = trueโ needsDisplay = true+ // Simple attributed string this view displays+ let content: NSAttributedString = NSAttributedString(+ string: "macOS 27 brings NSTextSelectionManager to any custom view.",+ attributes: [.font: NSFont.systemFont(ofSize: 14)]+ )โ // Manual tracking loop โ blocks the run loopโ var keepTracking = trueโ while keepTracking {โ guard let next = window?.nextEvent(matching: [.leftMouseDragged, .leftMouseUp]) else { break }โ let loc = convert(next.locationInWindow, from: nil)โ selectionEnd = characterIndex(at: loc)โ needsDisplay = trueโ if next.type == .leftMouseUp { keepTracking = false }+ override init(frame: NSRect) {+ super.init(frame: frame)+ setupTextSelectionManager()+ }++ required init?(coder: NSCoder) {+ super.init(coder: coder)+ setupTextSelectionManager()+ }++ private func setupTextSelectionManager() {+ // Attach the manager to this view, providing self as the data source+ textSelectionManager = NSTextSelectionManager(textSelectionDataSource: self)+ textSelectionManager.delegate = nil // optionally set a delegate+ }++ // MARK: - NSTextSelectionDataSource++ var documentRange: NSTextRange {+ // The full range of our content+ let loc = NSTextLocation(offset: 0)+ let end = NSTextLocation(offset: content.length)+ return NSTextRange(location: loc, end: end)!+ }++ func location(_ location: NSTextLocation,+ offsetBy offset: Int) -> NSTextLocation? {+ guard let loc = location as? NSTextLocation else { return nil }+ let newOffset = loc.offset + offset+ guard newOffset >= 0, newOffset <= content.length else { return nil }+ return NSTextLocation(offset: newOffset)+ }++ func offset(from: NSTextLocation,+ to: NSTextLocation) -> Int {+ guard let f = from as? NSTextLocation,+ let t = to as? NSTextLocation else { return 0 }+ return t.offset - f.offset+ }++ // MARK: - State Restoration++ override class var restorableStateKeyPaths: [String] { [] }++ override func encodeRestorableState(with coder: NSCoder) {+ super.encodeRestorableState(with: coder)+ // Encode the current selection range offsets+ if let selection = textSelectionManager.textSelections.first,+ let range = selection.textRanges.first,+ let start = range.location as? NSTextLocation,+ let end = range.endLocation as? NSTextLocation {+ coder.encode(start.offset, forKey: "selectionStart")+ coder.encode(end.offset, forKey: "selectionEnd")}โ isTracking = false}โ // Crude approximation โ no bidirectional text, no drag-and-drop, no toggle supportโ private func characterIndex(at point: NSPoint) -> Int {โ // placeholder geometry calculationโ return max(0, Int(point.x / 8))+ override func restoreState(with coder: NSCoder) {+ super.restoreState(with: coder)+ let start = coder.decodeInteger(forKey: "selectionStart")+ let end = coder.decodeInteger(forKey: "selectionEnd")+ guard end > start,+ let startLoc = NSTextLocation(offset: start) as NSTextLocation?,+ let endLoc = NSTextLocation(offset: end) as NSTextLocation?,+ let range = NSTextRange(location: startLoc, end: endLoc) else { return }+ let restored = NSTextSelection(range: range, affinity: .downstream, granularity: .character)+ textSelectionManager.textSelections = [restored]+ needsDisplay = true}+}โ // No built-in state restoration โ developer had to wire this up manuallyโ // No gesture recognizer interoperabilityโ // No cross-framework compatibility with SwiftUI or Catalyst+// MARK: - Minimal NSTextLocation helper++final class NSTextLocation: NSObject, NSTextLocation {+ let offset: Int+ init(offset: Int) { self.offset = offset }+ func compare(_ other: any AppKit.NSTextLocation) -> ComparisonResult {+ guard let o = other as? NSTextLocation else { return .orderedSame }+ if offset < o.offset { return .orderedAscending }+ if offset > o.offset { return .orderedDescending }+ return .orderedSame+ }}
NSTextSelectionManager is new in macOS 27 โ guard deployments targeting earlier OS versions. Always call the NSWindowRestoration completionHandler even on failure or AppKit will stall indefinitely. Setting preventsApplicationTerminationWhenModal to false on sheets that don't require user intervention is opt-in and should be done carefully to avoid data loss.
macOS only; not applicable to iOS or iPadOS targets
More iOS 27 APIs land every week.
Get notified when new capabilities are published โ no noise, just signal.