apple-text-appkit-vs-uikit

Use when comparing NSTextView and UITextView capabilities or porting editor behavior between macOS and iOS

AppKit vs UIKit Text Capabilities

Use this skill when the main question is which platform text stack can support a capability or editor behavior.

When to Use

  • You are comparing NSTextView and UITextView capabilities.
  • You are porting text code between macOS and iOS.
  • You need to know whether a feature gap is architectural or just an API difference.

Quick Decision

  • Desktop document-editor features, text tables, rulers, or services -> AppKit
  • Touch-first editing, modular interactions, or iOS-specific selection UI -> UIKit
  • Unsure whether the feature is platform-only or text-view-only -> keep reading

Core Guidance

NSTextView Can Do — UITextView Cannot

Rich Text Editing Panels (AppKit Only)

NSTextView provides built-in formatting panels with no UIKit equivalent:

PanelAPIPurpose
Font PanelusesFontPanelSystem Fonts window syncs with selection
RulerusesRuler / isRulerVisibleInteractive paragraph formatting (margins, tabs)
Link PanelorderFrontLinkPanel:Insert/edit hyperlinks
List PanelorderFrontListPanel:Configure list formatting
Table PanelorderFrontTablePanel:Insert/manipulate text tables
Spacing PanelorderFrontSpacingPanel:Paragraph spacing configuration
Substitutions PanelorderFrontSubstitutionsPanel:Smart quotes, dashes, text replacement config

Text Tables (NSTextTable / NSTextTableBlock)

AppKit-only classes. No UIKit equivalent exists.

let table = NSTextTable()
table.numberOfColumns = 3

let cell = NSTextTableBlock(table: table, startingRow: 0,
                             rowSpan: 1, startingColumn: 0, columnSpan: 1)
cell.backgroundColor = .lightGray
cell.setWidth(1.0, type: .absoluteValueType, for: .border)

let style = NSMutableParagraphStyle()
style.textBlocks = [cell]

Features: row/column spanning, per-cell borders/background, padding/margin control, automatic or fixed layout. Triggers TextKit 1 fallback.

Grammar Checking

// AppKit
textView.isGrammarCheckingEnabled = true
textView.toggleGrammarChecking(nil)

// UIKit — NO equivalent API
// Grammar checking is system-level only, not exposed to developers

Text Completion System

// AppKit — full completion infrastructure
textView.complete(nil)  // Invoke completion popup
textView.isAutomaticTextCompletionEnabled = true

// Override for custom completions:
override func completions(forPartialWordRange charRange: NSRange,
                           indexOfSelectedItem index: UnsafeMutablePointer<Int>) -> [String]? {
    return ["completion1", "completion2"]
}

// UIKit — NO built-in completion API
// Must build custom using UITextInput + overlay views

Spell Checking (Granular Control)

FeatureAppKitUIKit
Toggle continuous spell checkingisContinuousSpellCheckingEnabledspellCheckingType (.yes/.no)
Mark specific range as misspelledsetSpellingState(_:range:)Not available
Document tag managementspellCheckerDocumentTag()Not available
Grammar checkingisGrammarCheckingEnabledNot available

Smart Substitutions (Individual Toggle APIs)

FeatureAppKit APIUIKit Equivalent
Smart quotesisAutomaticQuoteSubstitutionEnabledsmartQuotesType (similar)
Smart dashesisAutomaticDashSubstitutionEnabledsmartDashesType (similar)
Text replacementisAutomaticTextReplacementEnabledSystem-level only
Auto-correctionisAutomaticSpellingCorrectionEnabledautocorrectionType (similar)
Link detectionisAutomaticLinkDetectionEnableddataDetectorTypes (different API)
Data detectionisAutomaticDataDetectionEnableddataDetectorTypes (different API)

AppKit provides individual toggle actions and a Substitutions Panel for user configuration. UIKit has simpler enum properties.

Services Menu Integration

NSTextView automatically participates in the macOS Services menu (send selected text to other apps):

// Automatic for NSTextView — implements NSServicesMenuRequestor
// Services like "Look Up in Dictionary", "Send via Mail" appear in Services menu

// For custom views:
override func validRequestor(forSendType sendType: NSPasteboard.PasteboardType?,
                              returnType: NSPasteboard.PasteboardType?) -> Any? {
    if sendType == .string { return self }
    return super.validRequestor(forSendType: sendType, returnType: returnType)
}

iOS has no Services concept.

Field Editor Architecture

Unique to AppKit. One shared NSTextView per window handles all NSTextField editing:

// Get the field editor
let fieldEditor = window.fieldEditor(true, for: textField) as? NSTextView

// Custom field editor
func windowWillReturnFieldEditor(_ sender: NSWindow, to client: Any?) -> Any? {
    if client is MySpecialTextField { return myCustomFieldEditor }
    return nil
}

Key implications:

  • Memory efficient (one editor for all fields)
  • One TK1 fallback in ANY field editor affects ALL fields in the window
  • Custom field editors can provide per-field customization

UIKit has no field editor. Each UITextField manages its own editing.

NSText Heritage (Direct RTF/RTFD)

NSTextView inherits from NSText, providing:

// Direct RTF/RTFD I/O
let rtfData = textView.rtf(from: range)
let rtfdData = textView.rtfd(from: range)
textView.replaceCharacters(in: range, withRTF: rtfData)
textView.replaceCharacters(in: range, withRTFD: rtfdData)
textView.writeRTFD(toFile: path, atomically: true)
textView.readRTFD(fromFile: path)

// Font/ruler pasteboard
textView.copyFont(nil)     // Copy font attributes
textView.pasteFont(nil)    // Apply font from pasteboard
textView.copyRuler(nil)    // Copy paragraph attributes
textView.pasteRuler(nil)   // Apply paragraph attributes

// Speech
textView.startSpeaking(nil)
textView.stopSpeaking(nil)

UIKit has none of these. You'd need to manually create RTF data, manage font pasteboards, or use AVSpeechSynthesizer.

Print Support

// AppKit — native print support via NSView
textView.printView(nil)  // Opens print dialog

// UIKit — separate print system
let formatter = UISimpleTextPrintFormatter(attributedText: textView.attributedText)
let controller = UIPrintInteractionController.shared
controller.printFormatter = formatter
controller.present(animated: true)

macOS Dark Mode gotcha: printView(nil) renders with the current appearance. In Dark Mode, this produces white text on white paper. Fix: create an off-screen text view sharing the same NSTextStorage with .appearance = NSAppearance(named: .aqua), and print from that.

iOS printing tiers: UISimpleTextPrintFormatter handles most cases. For custom headers/footers or multi-section documents, subclass UIPrintPageRenderer and compose formatters per page range.

Find and Replace

FeatureAppKitUIKit
APINSTextFinder (since OS X 10.7)UIFindInteraction (since iOS 16)
PropertiesusesFindBar, usesFindPanelfindInteraction, isFindInteractionEnabled
Incremental search✅ Built-in✅ Built-in
Custom providersNSTextFinderClient protocolUITextSearching protocol

Both platforms now support find, but AppKit's has existed much longer with more customization.

UITextView Can Do — NSTextView Cannot

Data Detector Types (Declarative)

// UIKit — single property, granular type selection
textView.dataDetectorTypes = [.link, .phoneNumber, .address, .calendarEvent,
                               .shipmentTrackingNumber, .flightNumber]
// Detected items become tappable automatically
// Only works when isEditable = false

// AppKit — Boolean toggles, less granular
textView.isAutomaticLinkDetectionEnabled = true
textView.isAutomaticDataDetectionEnabled = true
// Requires explicit checkTextInDocument: call

UITextInteraction (Modular Gestures)

// UIKit — add system text gestures to ANY UIView
let interaction = UITextInteraction(for: .editable)
interaction.textInput = customView  // Any UITextInput conformer
customView.addInteraction(interaction)

// AppKit — no equivalent modular component
// NSTextView has built-in gestures, but they can't be extracted

Text Item Interactions (iOS 17+)

// UIKit — rich interaction with links, attachments, tagged ranges
func textView(_ textView: UITextView,
              primaryActionFor textItem: UITextItem,
              defaultAction: UIAction) -> UIAction? {
    // Customize tap behavior
}

func textView(_ textView: UITextView,
              menuConfigurationFor textItem: UITextItem,
              defaultMenu: UIMenu) -> UITextItem.MenuConfiguration? {
    // Custom context menu
}

// Tag arbitrary ranges for interaction
let attrs: [NSAttributedString.Key: Any] = [
    .uiTextItemTag: "myCustomTag"
]

// AppKit — only has textView(_:clickedOnLink:at:)

UITextSelectionDisplayInteraction (iOS 17+)

System selection UI for custom views — cursor, handles, highlights:

let selectionDisplay = UITextSelectionDisplayInteraction(textInput: myView)
myView.addInteraction(selectionDisplay)
selectionDisplay.setNeedsSelectionUpdate()

AppKit gained NSTextInsertionIndicator (macOS Sonoma) for the cursor only, but nothing as comprehensive.

UITextLoupeSession (iOS 17+)

let session = UITextLoupeSession.begin(at: point, from: cursorView, in: self)
session.move(to: newPoint)
session.invalidate()

No AppKit equivalent — macOS doesn't use a loupe for text selection.

Architecture Differences

Inheritance

AppKit:  NSObject → NSResponder → NSView → NSText → NSTextView
         (Rich NSText base: RTF, font panel, ruler, field editor, speech)

UIKit:   NSObject → UIResponder → UIView → UIScrollView → UITextView
         (Scrolling built-in, but no text-specific base class)

Scrolling

AppKitUIKit
Built-in scroll❌ Must embed in NSScrollView✅ IS a UIScrollView
ConvenienceNSTextView.scrollableTextView()Always scrollable
Non-scrollingNSTextView is non-scrolling by defaultSet isScrollEnabled = false

Text Storage Access

AppKitUIKit
PropertytextStorage: NSTextStorage? (optional)textStorage: NSTextStorage (non-optional)
Full contentattributedString() methodattributedText property

Delegate Richness

NSTextViewDelegate is significantly richer than UITextViewDelegate:

  • Modify selection during changes
  • Intercept link clicks
  • Customize drag operations
  • Control tooltip display
  • Completion handling

UITextViewDelegate is minimal, though iOS 17 text item interactions narrowed the gap.

Writing Tools (Mostly Equivalent, Key Differences)

Both platforms support Writing Tools as of iOS 18 / macOS 15. The system view API is parallel (writingToolsBehavior, writingToolsAllowedInputOptions, isWritingToolsActive, matching delegate methods). Both require TextKit 2 for full inline experience.

Differences for custom text engines:

AspectUIKitAppKit
Coordinator classUIWritingToolsCoordinatorNSWritingToolsCoordinator
Attachmentview.addInteraction(coordinator)view.writingToolsCoordinator = coordinator
Preview typeUITargetedPreviewNSTextPreview
Path type[UIBezierPath][NSBezierPath]
Menu integrationAutomatic via UITextInteractionRequires NSServicesMenuRequestor (validRequestor(forSendType:returnType:), writeSelection(to:types:), readSelection(from:))

macOS 26 additions: automaticallyInsertsWritingToolsItems (default: true), .writingToolsItems for standard menu items, stock NSToolbarItem for toolbar integration.

Fallback Detection (Different Per Platform)

AspectUIKitAppKit
DetectionCheck textView.textLayoutManager == nilSame check + notifications
Breakpoint_UITextViewEnablingCompatibilityModeNot available
NotificationsNoneNSTextView.willSwitchToNSLayoutManagerNotification, NSTextView.didSwitchToNSLayoutManagerNotification
Console logYes (system logs the switch)Yes

Quick Decision Guide

NeedPlatform
Text tablesAppKit only (NSTextTable)
Grammar checking APIAppKit only
Text completion APIAppKit only
Services menuAppKit only (macOS concept)
Font panel integrationAppKit only
Interactive rulerAppKit only
Direct RTF file I/OAppKit only (NSText heritage)
Declarative data detectorsUIKit better (dataDetectorTypes)
Modular text interactionUIKit only (UITextInteraction)
Text item context menusUIKit only (iOS 17+)
Selection display componentUIKit only (UITextSelectionDisplayInteraction)
Multi-page/multi-columnAppKit better (historically)
Built-in scrollingUIKit (IS a scroll view)

Related Skills

  • Use /skill apple-text-views when the real question is which view class to adopt.
  • Use /skill apple-text-representable when SwiftUI wrapping is part of the platform decision.
  • Use /skill apple-text-writing-tools for Apple Intelligence editor differences.