Skip to content

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.

Workflow Skills

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.

  • 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
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

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

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+
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
// 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

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

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.

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

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
}
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"
// Teach a word (persists across app launches)
UITextChecker.learnWord("SwiftUI")
// Check if learned
UITextChecker.hasLearnedWord("SwiftUI") // true
// Forget a word
UITextChecker.unlearnWord("SwiftUI")
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)
let languages = UITextChecker.availableLanguages
// ["en", "fr", "de", "es", "it", "pt", "nl", "sv", "da", ...]

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

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
)
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)
}
}

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)
// 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.

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)

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.
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
  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.

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.

  • 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
SKILL.md
---
name: apple-text-spell-autocorrect
description: 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)
```swift
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)
```swift
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
```swift
// 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**
```swift
// 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
```swift
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
```swift
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
```swift
// 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
```swift
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
```swift
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
```swift
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)
```swift
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:
```swift
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)
```swift
// 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
| 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.