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
| 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:
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
| 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
- 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.
Related Skills
- Use
/skill apple-text-input-reffor the full UITextInput protocol and inputDelegate requirements. - Use
/skill apple-text-interactionfor UITextInteraction setup and gesture handling. - Use
/skill apple-text-appkit-vs-uikitfor broader platform capability comparison. - Use
/skill apple-text-viewswhen the real question is whether to use UITextView vs a custom view.