Spell Checking & Autocorrect
Use when implementing spell checking, autocorrect, or text completion in Apple text editors, including UITextChecker, NSSpellChecker, UITextInputTraits, or custom correction UI. Reach for this when the problem is spelling or correction behavior, not generic text interaction.
Use when implementing spell checking, autocorrect, or text completion in Apple text editors, including UITextChecker, NSSpellChecker, UITextInputTraits, or custom correction UI. Reach for this when the problem is spelling or correction behavior, not generic text interaction.
Family: Editor Features And Interaction
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
Section titled “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
Section titled “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 = .noCore Guidance
Section titled “Core Guidance”UITextView / NSTextView (Built-In)
Section titled “UITextView / NSTextView (Built-In)”Standard text views handle spell checking automatically. Configure with properties:
UITextView (iOS)
Section titled “UITextView (iOS)”textView.spellCheckingType = .yes // .default, .no, .yestextView.autocorrectionType = .yes // .default, .no, .yestextView.autocapitalizationType = .sentencestextView.smartQuotesType = .yestextView.smartDashesType = .yestextView.smartInsertDeleteType = .yestextView.inlinePredictionType = .yes // iOS 17+NSTextView (macOS)
Section titled “NSTextView (macOS)”textView.isContinuousSpellCheckingEnabled = truetextView.isGrammarCheckingEnabled = true // macOS only — no iOS equivalenttextView.isAutomaticSpellingCorrectionEnabled = truetextView.isAutomaticQuoteSubstitutionEnabled = truetextView.isAutomaticDashSubstitutionEnabled = truetextView.isAutomaticTextReplacementEnabled = truetextView.isAutomaticTextCompletionEnabled = truetextView.isAutomaticLinkDetectionEnabled = truetextView.isAutomaticDataDetectionEnabled = trueDisabling for Code Editors
Section titled “Disabling for Code Editors”// iOStextView.spellCheckingType = .notextView.autocorrectionType = .notextView.autocapitalizationType = .nonetextView.smartQuotesType = .notextView.smartDashesType = .no
// macOStextView.isContinuousSpellCheckingEnabled = falsetextView.isGrammarCheckingEnabled = falsetextView.isAutomaticSpellingCorrectionEnabled = falsetextView.isAutomaticTextCompletionEnabled = falseThe UITextInteraction Trap (Custom Views)
Section titled “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
Section titled “Workarounds”Option A: Disable system spell checking, build your own
// Disable system spell checking on your custom viewvar spellCheckingType: UITextSpellCheckingType { .no }var autocorrectionType: UITextAutocorrectionType { .no }
// Use UITextChecker for your own spell check logiclet 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
Section titled “UITextChecker”Standalone spell checking class. Not tied to any view. Works on iOS and macOS.
Basic Spell Checking
Section titled “Basic Spell Checking”let checker = UITextChecker()let text = "Ths is a tset"let range = NSRange(location: 0, length: (text as NSString).length)
var offset = 0while 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
Section titled “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
Section titled “Custom Dictionary”// Teach a word (persists across app launches)UITextChecker.learnWord("SwiftUI")
// Check if learnedUITextChecker.hasLearnedWord("SwiftUI") // true
// Forget a wordUITextChecker.unlearnWord("SwiftUI")Per-Session Ignore List
Section titled “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
Section titled “Available Languages”let languages = UITextChecker.availableLanguages// ["en", "fr", "de", "es", "it", "pt", "nl", "sv", "da", ...]NSSpellChecker (macOS)
Section titled “NSSpellChecker (macOS)”The macOS spell checker is significantly more capable. It’s a singleton service.
Basic Usage
Section titled “Basic Usage”let spellChecker = NSSpellChecker.shared
// Simple checklet 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)
Section titled “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
Section titled “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)
Section titled “Marking Misspelled Ranges (AppKit Only)”// Visually mark a range as misspelled (red underline)textView.setSpellingState(NSSpellingStateSpellingFlag, range: misspelledRange)
// Or use the .spellingState attributed string keylet attrs: [NSAttributedString.Key: Any] = [ .spellingState: NSSpellingStateSpellingFlag]No iOS equivalent of setSpellingState. On iOS, the system manages spell check underlines internally.
Platform Comparison
Section titled “Platform Comparison”| Feature | iOS (UITextView) | macOS (NSTextView) |
|---|---|---|
| Continuous spell checking | Yes (spellCheckingType) | Yes (isContinuousSpellCheckingEnabled) |
| Grammar checking | No API | Yes (isGrammarCheckingEnabled) |
| Mark specific range as misspelled | No | Yes (setSpellingState) |
| Spell document tags | UITextChecker only | Full support |
| Async checking | No | Yes (requestChecking) |
| Text completion popup | No built-in | Yes (complete(_:)) |
| System Spelling panel | No | Yes (orderFrontSpellingPanel:) |
| Substitutions panel | No | Yes (orderFrontSubstitutionsPanel:) |
| Individual toggle APIs | Enum properties | Boolean properties per feature |
| Spell check pre-existing text | Only near edits | Yes (full document) |
How Autocorrect Works with UITextInput
Section titled “How Autocorrect Works with UITextInput”For autocorrect to function in a custom UITextInput view, the system needs:
UITextInputTraitsproperties —spellCheckingTypeandautocorrectionTypemust be.yesor.defaultinputDelegatecallbacks — You MUST calltextWillChange/textDidChangeandselectionWillChange/selectionDidChangeon every change. Failing to do so desyncs the autocorrect system silently.- Correct geometry —
caretRect(for:)andfirstRect(for:)must return accurate rects. The autocorrect bubble and spell check popover are positioned using these. UITextInteractionadded — The interaction provides the gesture recognizers that trigger the spell check popover.
When Autocorrect Silently Breaks
Section titled “When Autocorrect Silently Breaks”| Symptom | Cause |
|---|---|
| No autocorrect suggestions appear | inputDelegate.textDidChange not called |
| Autocorrect bubble appears in wrong position | caretRect(for:) returns wrong rect |
Changing autocorrectionType has no effect | Changed while view is first responder (must resign first) |
| Red underlines appear but corrections don’t apply | The UITextInteraction private API trap (see above) |
| Spell check only works on new text, not pre-existing | iOS only checks near edits, not the full document |
Common Pitfalls
Section titled “Common Pitfalls”- 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.
- Not calling
inputDelegatemethods — The autocorrect system desyncs silently. No error, no crash — just stops working. - Changing traits while first responder —
spellCheckingTypeandautocorrectionTypechanges only take effect when the view is not first responder. Resign and re-become first responder. - Expecting
completionssorted by probability —UITextChecker.completions(forPartialWordRange:)returns alphabetical order despite Apple’s documentation claiming probability-based sorting. - Not managing spell document tags (macOS) — Forgetting
closeSpellDocument(withTag:)leaks ignore lists. - Code editors with spell checking on — Set
spellCheckingType = .nofor code editors. Red underlines on variable names are distracting and wrong. - NSSpellChecker on background thread — It’s main-thread only. Use
requestCheckingfor async work on large documents. - Expecting iOS grammar checking — There is no grammar checking API on iOS. It’s macOS only.
Documentation Scope
Section titled “Documentation Scope”This page documents the apple-text-spell-autocorrect workflow skill. Use it when the job is a guided review, implementation flow, or integration pass instead of a single API lookup.
Related
Section titled “Related”apple-text-input-ref: Use when the user already knows the problem lives in the text input system and needs exact UITextInput, UIKeyInput, NSTextInputClient, marked-text, or selection-UI behavior. Reach for this when implementing or debugging custom text input plumbing, not high-level editor interactions alone.apple-text-interaction: Use when customizing text-editor interactions in UIKit, such as selection behavior, edit menus, link taps, gestures, cursor appearance, or long-press actions. Reach for this when the problem is interaction behavior, not custom text input protocol plumbing.apple-text-appkit-vs-uikit: Use when comparing NSTextView and UITextView capabilities, porting editor behavior between macOS and iOS, or deciding whether an AppKit-only text feature has a UIKit equivalent. Reach for this when the main task is platform capability tradeoffs, not TextKit 1 vs TextKit 2 choice.
Full SKILL.md source
---name: apple-text-spell-autocorrectdescription: Use when implementing spell checking, autocorrect, or text completion in Apple text editors, including UITextChecker, NSSpellChecker, UITextInputTraits, or custom correction UI. Reach for this when the problem is spelling or correction behavior, not generic text interaction.license: MIT---
# 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)
```swifttextView.spellCheckingType = .yes // .default, .no, .yestextView.autocorrectionType = .yes // .default, .no, .yestextView.autocapitalizationType = .sentencestextView.smartQuotesType = .yestextView.smartDashesType = .yestextView.smartInsertDeleteType = .yestextView.inlinePredictionType = .yes // iOS 17+```
### NSTextView (macOS)
```swifttextView.isContinuousSpellCheckingEnabled = truetextView.isGrammarCheckingEnabled = true // macOS only — no iOS equivalenttextView.isAutomaticSpellingCorrectionEnabled = truetextView.isAutomaticQuoteSubstitutionEnabled = truetextView.isAutomaticDashSubstitutionEnabled = truetextView.isAutomaticTextReplacementEnabled = truetextView.isAutomaticTextCompletionEnabled = truetextView.isAutomaticLinkDetectionEnabled = truetextView.isAutomaticDataDetectionEnabled = true```
### Disabling for Code Editors
```swift// iOStextView.spellCheckingType = .notextView.autocorrectionType = .notextView.autocapitalizationType = .nonetextView.smartQuotesType = .notextView.smartDashesType = .no
// macOStextView.isContinuousSpellCheckingEnabled = falsetextView.isGrammarCheckingEnabled = falsetextView.isAutomaticSpellingCorrectionEnabled = falsetextView.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**
```swift// Disable system spell checking on your custom viewvar spellCheckingType: UITextSpellCheckingType { .no }var autocorrectionType: UITextAutocorrectionType { .no }
// Use UITextChecker for your own spell check logiclet 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
```swiftlet checker = UITextChecker()let text = "Ths is a tset"let range = NSRange(location: 0, length: (text as NSString).length)
var offset = 0while 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
```swiftlet 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
```swift// Teach a word (persists across app launches)UITextChecker.learnWord("SwiftUI")
// Check if learnedUITextChecker.hasLearnedWord("SwiftUI") // true
// Forget a wordUITextChecker.unlearnWord("SwiftUI")```
### Per-Session Ignore List
```swiftlet 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
```swiftlet 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
```swiftlet spellChecker = NSSpellChecker.shared
// Simple checklet 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)
```swiftlet 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:
```swiftlet tag = NSSpellChecker.uniqueSpellDocumentTag()// ... use tag for all checking in this document ...
// When document closes:NSSpellChecker.shared.closeSpellDocument(withTag: tag)```
### Marking Misspelled Ranges (AppKit Only)
```swift// Visually mark a range as misspelled (red underline)textView.setSpellingState(NSSpellingStateSpellingFlag, range: misspelledRange)
// Or use the .spellingState attributed string keylet attrs: [NSAttributedString.Key: Any] = [ .spellingState: NSSpellingStateSpellingFlag]```
No iOS equivalent of `setSpellingState`. On iOS, the system manages spell check underlines internally.
## Platform Comparison
| Feature | iOS (UITextView) | macOS (NSTextView) ||---------|------------------|-------------------|| Continuous spell checking | Yes (`spellCheckingType`) | Yes (`isContinuousSpellCheckingEnabled`) || Grammar checking | No API | Yes (`isGrammarCheckingEnabled`) || Mark specific range as misspelled | No | Yes (`setSpellingState`) || Spell document tags | UITextChecker only | Full support || Async checking | No | Yes (`requestChecking`) || Text completion popup | No built-in | Yes (`complete(_:)`) || System Spelling panel | No | Yes (`orderFrontSpellingPanel:`) || Substitutions panel | No | Yes (`orderFrontSubstitutionsPanel:`) || Individual toggle APIs | Enum properties | Boolean properties per feature || Spell check pre-existing text | Only near edits | Yes (full document) |
## How Autocorrect Works with UITextInput
For autocorrect to function in a custom `UITextInput` view, the system needs:
1. **`UITextInputTraits` properties** — `spellCheckingType` 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 geometry** — `caretRect(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
| Symptom | Cause ||---------|-------|| No autocorrect suggestions appear | `inputDelegate.textDidChange` not called || Autocorrect bubble appears in wrong position | `caretRect(for:)` returns wrong rect || Changing `autocorrectionType` has no effect | Changed while view is first responder (must resign first) || Red underlines appear but corrections don't apply | The UITextInteraction private API trap (see above) || Spell check only works on new text, not pre-existing | iOS 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 responder** — `spellCheckingType` and `autocorrectionType` changes only take effect when the view is not first responder. Resign and re-become first responder.4. **Expecting `completions` sorted by probability** — `UITextChecker.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.