apple-text-spell-autocorrect

Use when implementing spell checking, autocorrect, or text completion — UITextChecker, NSSpellChecker, UITextInputTraits

Spell Checking & Autocorrect

Use this skill when the main question is how spell checking, autocorrect, or text completion works in Apple text editors, or when custom views need these features.

When to Use

  • Configuring spell checking or autocorrect on text views
  • Building a custom UITextInput view that needs spell checking
  • Implementing custom word completion with UITextChecker
  • Debugging autocorrect not working in a custom editor
  • Comparing spell checking capabilities between iOS and macOS

Quick Decision

Using UITextView or NSTextView?
    → Spell checking works automatically. Configure via properties.

Building a custom UITextInput view?
    → READ THE TRAP SECTION BELOW before enabling spell checking.

Need custom word suggestions or spell checking logic?
    → UITextChecker (iOS) or NSSpellChecker (macOS)

Need to disable spell checking for a code editor?
    → Set spellCheckingType = .no and autocorrectionType = .no

Core Guidance

UITextView / NSTextView (Built-In)

Standard text views handle spell checking automatically. Configure with properties:

UITextView (iOS)

textView.spellCheckingType = .yes       // .default, .no, .yes
textView.autocorrectionType = .yes      // .default, .no, .yes
textView.autocapitalizationType = .sentences
textView.smartQuotesType = .yes
textView.smartDashesType = .yes
textView.smartInsertDeleteType = .yes
textView.inlinePredictionType = .yes    // iOS 17+

NSTextView (macOS)

textView.isContinuousSpellCheckingEnabled = true
textView.isGrammarCheckingEnabled = true           // macOS only — no iOS equivalent
textView.isAutomaticSpellingCorrectionEnabled = true
textView.isAutomaticQuoteSubstitutionEnabled = true
textView.isAutomaticDashSubstitutionEnabled = true
textView.isAutomaticTextReplacementEnabled = true
textView.isAutomaticTextCompletionEnabled = true
textView.isAutomaticLinkDetectionEnabled = true
textView.isAutomaticDataDetectionEnabled = true

Disabling for Code Editors

// iOS
textView.spellCheckingType = .no
textView.autocorrectionType = .no
textView.autocapitalizationType = .none
textView.smartQuotesType = .no
textView.smartDashesType = .no

// macOS
textView.isContinuousSpellCheckingEnabled = false
textView.isGrammarCheckingEnabled = false
textView.isAutomaticSpellingCorrectionEnabled = false
textView.isAutomaticTextCompletionEnabled = false

The UITextInteraction Trap (Custom Views)

This is the single most important thing in this skill.

If you build a custom UITextInput view and add UITextInteraction, spell checking underlines appear correctly. The system detects misspelled words, draws red underlines, and shows a correction popover when the user taps.

But tapping a correction invokes a private API (UITextReplacement). Your custom view cannot apply the correction without accessing private symbols. This means:

  • Spell check underlines appear ✅
  • Correction popover appears ✅
  • Tapping a correction does nothing or crashes
  • Using the private API gets rejected from the App Store

Workarounds

Option A: Disable system spell checking, build your own

// Disable system spell checking on your custom view
var spellCheckingType: UITextSpellCheckingType { .no }
var autocorrectionType: UITextAutocorrectionType { .no }

// Use UITextChecker for your own spell check logic
let checker = UITextChecker()
let misspelledRange = checker.rangeOfMisspelledWord(
    in: text, range: NSRange(location: 0, length: (text as NSString).length),
    startingAt: 0, wrap: false, language: "en"
)

if misspelledRange.location != NSNotFound {
    let guesses = checker.guesses(forWordRange: misspelledRange, in: text, language: "en")
    // Show your own correction UI (popover, menu, etc.)
}

Option B: Accept panel-only corrections

Leave spellCheckingType = .yes but accept that inline corrections won't apply. Users can still use the system spell check panel (Edit menu → Spelling) on macOS.

Option C: Use UITextView instead

If spell checking is important, consider using UITextView (which handles corrections correctly) rather than a fully custom UITextInput view.

UITextChecker

Standalone spell checking class. Not tied to any view. Works on iOS and macOS.

Basic Spell Checking

let checker = UITextChecker()
let text = "Ths is a tset"
let range = NSRange(location: 0, length: (text as NSString).length)

var offset = 0
while offset < (text as NSString).length {
    let misspelled = checker.rangeOfMisspelledWord(
        in: text, range: range, startingAt: offset,
        wrap: false, language: "en"
    )

    if misspelled.location == NSNotFound { break }

    let word = (text as NSString).substring(with: misspelled)
    let guesses = checker.guesses(forWordRange: misspelled, in: text, language: "en") ?? []
    print("'\(word)' → suggestions: \(guesses)")

    offset = misspelled.location + misspelled.length
}

Word Completion

let partial = "prog"
let completions = checker.completions(
    forPartialWordRange: NSRange(location: 0, length: (partial as NSString).length),
    in: partial, language: "en"
) ?? []
// Returns: ["program", "programming", "progress", ...]
// NOTE: sorted alphabetically despite docs saying "by probability"

Custom Dictionary

// Teach a word (persists across app launches)
UITextChecker.learnWord("SwiftUI")

// Check if learned
UITextChecker.hasLearnedWord("SwiftUI")  // true

// Forget a word
UITextChecker.unlearnWord("SwiftUI")

Per-Session Ignore List

let tag = UITextChecker.uniqueSpellDocumentTag()
checker.ignoreWord("xyzzy", inSpellDocumentWithTag: tag)
let ignored = checker.ignoredWords(inSpellDocumentWithTag: tag)
// Don't forget to close when done (macOS pattern, good practice on iOS too)

Available Languages

let languages = UITextChecker.availableLanguages
// ["en", "fr", "de", "es", "it", "pt", "nl", "sv", "da", ...]

NSSpellChecker (macOS)

The macOS spell checker is significantly more capable. It's a singleton service.

Basic Usage

let spellChecker = NSSpellChecker.shared

// Simple check
let text = "Ths is a tset"
let misspelled = spellChecker.checkSpelling(of: text, startingAt: 0)

// Unified checking (spelling + grammar + data detection)
let results = spellChecker.check(
    text, range: NSRange(location: 0, length: (text as NSString).length),
    types: NSTextCheckingAllTypes, options: nil,
    inSpellDocumentWithTag: 0, orthography: nil, wordCount: nil
)

Async Checking (Large Documents)

let tag = NSSpellChecker.uniqueSpellDocumentTag()
spellChecker.requestChecking(
    of: text,
    range: NSRange(location: 0, length: (text as NSString).length),
    types: .correctionIndicator,
    options: nil,
    inSpellDocumentWithTag: tag
) { sequenceNumber, results, orthography, wordCount in
    // Apply results on main thread
    DispatchQueue.main.async {
        self.applySpellCheckResults(results)
    }
}

Spell Document Tags

Isolate ignore lists per document:

let tag = NSSpellChecker.uniqueSpellDocumentTag()
// ... use tag for all checking in this document ...

// When document closes:
NSSpellChecker.shared.closeSpellDocument(withTag: tag)

Marking Misspelled Ranges (AppKit Only)

// Visually mark a range as misspelled (red underline)
textView.setSpellingState(NSSpellingStateSpellingFlag, range: misspelledRange)

// Or use the .spellingState attributed string key
let attrs: [NSAttributedString.Key: Any] = [
    .spellingState: NSSpellingStateSpellingFlag
]

No iOS equivalent of setSpellingState. On iOS, the system manages spell check underlines internally.

Platform Comparison

FeatureiOS (UITextView)macOS (NSTextView)
Continuous spell checkingYes (spellCheckingType)Yes (isContinuousSpellCheckingEnabled)
Grammar checkingNo APIYes (isGrammarCheckingEnabled)
Mark specific range as misspelledNoYes (setSpellingState)
Spell document tagsUITextChecker onlyFull support
Async checkingNoYes (requestChecking)
Text completion popupNo built-inYes (complete(_:))
System Spelling panelNoYes (orderFrontSpellingPanel:)
Substitutions panelNoYes (orderFrontSubstitutionsPanel:)
Individual toggle APIsEnum propertiesBoolean properties per feature
Spell check pre-existing textOnly near editsYes (full document)

How Autocorrect Works with UITextInput

For autocorrect to function in a custom UITextInput view, the system needs:

  1. UITextInputTraits propertiesspellCheckingType and autocorrectionType must be .yes or .default
  2. inputDelegate callbacks — You MUST call textWillChange/textDidChange and selectionWillChange/selectionDidChange on every change. Failing to do so desyncs the autocorrect system silently.
  3. Correct geometrycaretRect(for:) and firstRect(for:) must return accurate rects. The autocorrect bubble and spell check popover are positioned using these.
  4. UITextInteraction added — The interaction provides the gesture recognizers that trigger the spell check popover.

When Autocorrect Silently Breaks

SymptomCause
No autocorrect suggestions appearinputDelegate.textDidChange not called
Autocorrect bubble appears in wrong positioncaretRect(for:) returns wrong rect
Changing autocorrectionType has no effectChanged while view is first responder (must resign first)
Red underlines appear but corrections don't applyThe UITextInteraction private API trap (see above)
Spell check only works on new text, not pre-existingiOS only checks near edits, not the full document

Common Pitfalls

  1. The UITextInteraction correction trap — Spell check underlines work in custom UITextInput views, but applying corrections uses private API. Either disable system spell checking and use UITextChecker directly, or use UITextView.
  2. Not calling inputDelegate methods — The autocorrect system desyncs silently. No error, no crash — just stops working.
  3. Changing traits while first responderspellCheckingType and autocorrectionType changes only take effect when the view is not first responder. Resign and re-become first responder.
  4. Expecting completions sorted by probabilityUITextChecker.completions(forPartialWordRange:) returns alphabetical order despite Apple's documentation claiming probability-based sorting.
  5. Not managing spell document tags (macOS) — Forgetting closeSpellDocument(withTag:) leaks ignore lists.
  6. Code editors with spell checking on — Set spellCheckingType = .no for code editors. Red underlines on variable names are distracting and wrong.
  7. NSSpellChecker on background thread — It's main-thread only. Use requestChecking for async work on large documents.
  8. Expecting iOS grammar checking — There is no grammar checking API on iOS. It's macOS only.

Related Skills

  • Use /skill apple-text-input-ref for the full UITextInput protocol and inputDelegate requirements.
  • Use /skill apple-text-interaction for UITextInteraction setup and gesture handling.
  • Use /skill apple-text-appkit-vs-uikit for broader platform capability comparison.
  • Use /skill apple-text-views when the real question is whether to use UITextView vs a custom view.