Spell Check in SwiftUI Using UITextChecker
iOS provides a built in spellchecker using UITextChecker
. UITextChecker
class can be used to check a string for misspelled words. This class has been around for a while, it came into picture with the release of iOS 3.2.
UITextChecker
spell-check uses lexicon for specific language to perform spelling correction for supported languages.
In this article, we will use UITextChecker
with SwiftUI so let’s get started.
For our app, we will have a TextEditor
where user can enter text and bottom part of the screen will show suggestions in a List view
struct DevTechieSpellChecker: View {
@State private var text = ""
@State private var gussess: [String]? = []
var body: some View {
NavigationStack {
VStack {
TextEditor(text: $text)
.padding()
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray.opacity(0.5)))
if let gussess = gussess {
List(gussess, id: \.self) { guess in
HStack {
Text(guess)
Spacer()
}
.contentShape(Rectangle())
.onTapGesture {
}
}
}
}
.padding()
.navigationTitle("DevTechie")
}
}
}
We will create an instance of UITextChecker
. Having one instance per document is fine but we can have single instance to spell-check different parts of the code. We will create just one instance at the struct level.
struct DevTechieSpellChecker: View {
@State private var text = ""
@State private var guesses: [String]? = []
let textChecker = UITextChecker()
var body: some View {
NavigationStack {
VStack {
TextEditor(text: $text)
.padding()
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray.opacity(0.5)))
if let gussess = guesses {
List(gussess, id: \.self) { guess in
HStack {
Text(guess)
Spacer()
}
.contentShape(Rectangle())
.onTapGesture {
}
}
}
}
.padding()
.navigationTitle("DevTechie")
}
}
}
Next, we will attach an onChange
observer for text being typed by the user so UITextChecker
can start telling us spelling for misspelled words. We will use guesses array to store the values returned by the UITextChecker
.
.onChange(of: text, perform: { newValue in
let missspelledRange = textChecker.rangeOfMisspelledWord(in: text, range: NSRange(0..<text.utf16.count), startingAt: 0, wrap: false, language: "en_US")
if missspelledRange.location != NSNotFound {
guesses = textChecker.guesses(forWordRange: missspelledRange, in: text, language: "en_US")
}
})
rangeOfMisspelledWord(in:range:startingAt:wrap:language:)
initiates a search of a range of a string for a misspelled word. It takes following parameters and returns the range of the first misspelled word encountered or {NSNotFound, 0}
if none is found.
stringToCheck
The string to check for misspelled words.
range
The range of stringToCheck
to check for a misspelled word.
startingOffset
The offset within range
of stringToCheck
to begin checking for misspelled words.
wrapFla
Set this value to true to continue checking from the beginning of range
if no misspelled word is found between startingOffset
and the end of range
. Specify false
to have spell-checking end at the end of range
.
language
The language of the of words to be checked for correct spelling. This string is a ISO 639–1 language code or a combined ISO 639–1 language code and ISO 3166–1 regional code (for example, fr_FR
).
Our code should look like this:
struct DevTechieSpellChecker: View {
@State private var text = ""
@State private var guesses: [String]? = []
let textChecker = UITextChecker()
var body: some View {
NavigationStack {
VStack {
TextEditor(text: $text)
.padding()
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray.opacity(0.5)))
if let gussess = guesses {
List(gussess, id: \.self) { guess in
HStack {
Text(guess)
Spacer()
}
.contentShape(Rectangle())
.onTapGesture {
}
}
}
}
.onChange(of: text, perform: { newValue in
let missspelledRange = textChecker.rangeOfMisspelledWord(in: text, range: NSRange(0..<text.utf16.count), startingAt: 0, wrap: false, language: "en_US")
if missspelledRange.location != NSNotFound {
guesses = textChecker.guesses(forWordRange: missspelledRange, in: text, language: "en_US")
}
})
.padding()
.navigationTitle("DevTechie")
}
}
}
Build and run
It will be great if we can select item from the list of suggestions and autocomplete the word.
We will build a basic autocomplete and to accomplish selection of a suggested word, we will first take the string and divide it in an array of words separated by space.
var words = text.components(separatedBy: " ")
Next, we will remove the last entry from the array of words
words.removeLast()
and will append the guess to the words array.
words.append(guess)
Next, we will replace the text by joining words in the array.
text = words.joined(separator: " ")
Our code should look like this.
struct DevTechieSpellChecker: View {
@State private var text = ""
@State private var guesses: [String]? = []
let textChecker = UITextChecker()
var body: some View {
NavigationStack {
VStack {
TextEditor(text: $text)
.padding()
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray.opacity(0.5)))
if let gussess = guesses {
List(gussess, id: \.self) { guess in
HStack {
Text(guess)
Spacer()
}
.contentShape(Rectangle())
.onTapGesture {
var words = text.components(separatedBy: " ")
words.removeLast()
words.append(guess)
text = words.joined(separator: " ")
}
}
}
}
.onChange(of: text, perform: { newValue in
let missspelledRange = textChecker.rangeOfMisspelledWord(in: text, range: NSRange(0..<text.utf16.count), startingAt: 0, wrap: false, language: "en_US")
if missspelledRange.location != NSNotFound {
guesses = textChecker.guesses(forWordRange: missspelledRange, in: text, language: "en_US")
}
})
.padding()
.navigationTitle("DevTechie")
}
}
}
Build and run
UITextChecker
includes ways for iOS to learn your vocabulary as well. By using class function called learnWord(_ string:)
we can make UITextChecker
learn new stuff.
struct DevTechieSpellChecker: View {
@State private var text = ""
@State private var gusses: [String]? = []
let textChecker = UITextChecker()
var body: some View {
NavigationStack {
VStack {
TextEditor(text: $text)
.padding()
.overlay(RoundedRectangle(cornerRadius: 10).stroke(Color.gray.opacity(0.5)))
if let gusses = gussess {
List(gussess, id: \.self) { guess in
HStack {
Text(guess)
Spacer()
}
.contentShape(Rectangle())
.onTapGesture {
var words = text.components(separatedBy: " ")
words.removeLast()
words.append(guess)
text = words.joined(separator: " ")
}
}
}
}
.onChange(of: text, perform: { newValue in
let missspelledRange = textChecker.rangeOfMisspelledWord(in: text, range: NSRange(0..<text.utf16.count), startingAt: 0, wrap: false, language: "en_US")
if missspelledRange.location != NSNotFound {
gussess = textChecker.guesses(forWordRange: missspelledRange, in: text, language: "en_US")
}
})
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Learn") {
UITextChecker.learnWord(text)
print(UITextChecker.hasLearnedWord(text))
}
}
}
.padding()
.navigationTitle("DevTechie")
}
}
}