20

I want to use the autocorrection and shortcut list like default English keyboard with my custom keyboard. I check the in keyboard document but don't know how to use it.

In keyboard documentation.

Every custom keyboard (independent of the value of its RequestsOpenAccess key) has access to a basic autocorrection lexicon through the UILexicon class. Make use of this class, along with a lexicon of your own design, to provide suggestions and autocorrections as users are entering text. The UILexicon object contains words from various sources, including:

  • Unpaired first names and last names from the user’s Address Book database
  • Text shortcuts defined in the Settings > General > Keyboard > Shortcuts list
  • A common words dictionary

How to access shortcut list and input from our dictionary in Objective-C?

How to use UILexicon with requestSupplementaryLexiconWithCompletion?

LE SANG
  • 10,955
  • 7
  • 59
  • 78
  • 1
    @rickster has a brilliant answer [here](http://stackoverflow.com/questions/24916369/objective-c-uilexicon). Have you looked at it? I can't fathom the 500 point bounty being deserved by anyone else since the answer already exists. – brandonscript Nov 17 '14 at 23:12
  • Thank you @remus! Maybe that question was closed so I can't search. – LE SANG Nov 17 '14 at 23:27

9 Answers9

23

Implementing the lexicon would look pretty much like this:

  1. Use requestSupplementaryLexiconWithCompletion() to get the lexicon upon launch once.
  2. Each type text is inputted add it to a NSString (tracking the current word)
  3. When user presses space (end of curent word) check the string against the lexicon
  4. If it's a match count the number of characters and delete that number of characters
  5. Input the suggestion suggested by the lexicon
  6. Clear the string and start again

Additionally you could also use UITextChecker to offer more advanced auto-correct features.

Code (in Objective-C, this may not be 100% accurate I wrote in SO while on the bus but it should do):

UILexicon *lexicon;
NSString *currentString;

-(void)viewDidLoad {
     [self requestSupplementaryLexiconWithCompletion:^(UILexicon *receivedLexicon) {
         self.lexicon = receivedLexicon;
     }];
}

-(IBAction)myTypingAction:(UIButton *)sender {
    [documentProxy insertText:sender.title]; 
    [currentString stringByAppendingString:sender.title];
}

-(IBAction)space {
   [documentProxy insertText:@" "];
   for (UILexiconEntry *lexiconEntry in lexicon.entries) {
       if (lexiconEntry.userInput isEqualToString:currentString) {
            for (int i = 0; currentString.length >=i ; i++) { 
                 [documentProxy deleteTextBackwards];
            }
            [documentProxy insertText:lexiconEntry.documentText];
            currentString = @"";  
        }
    } 
}

Feel free to comment if you have any more questions.

Source: Personal experience with iOS 8 keyboards and UILexicon

donkey
  • 1,343
  • 13
  • 32
  • 2
    If it matches a autocorrected word, then why would you delete it and then apply the same word? Is Entries like a list of wrong words or how would this change a wrong word to the correct one? – Maximilian Litteral Nov 15 '14 at 15:27
  • Entries is a list of words which should be replaced if other words are entered for example: I entered sry and it will correct it to sorry because thats what I defined as a shortcut. These entries are not for auto-correction. – donkey Nov 15 '14 at 20:31
  • Well it is part of autocorrection but not is not the most advanced type. – donkey Nov 15 '14 at 23:13
  • This is awesome, only trouble I seem to be getting is that all the strings inside this lexicon are returned as lowercase (including contact names). Is there a way to get the raw case of each string or should we just adjust it to our needs (i.e. ```myString = myLexiconEntry.documentText.capitalizedString```? – KingPolygon Nov 18 '14 at 00:09
  • 1
    Oh and should this be done on a background thread (excluding the update to our UI) I'm getting a nasty lag when typing... – KingPolygon Nov 18 '14 at 00:31
  • This should be executed on a background thread and not too sure about capitalization i just adjusted it to my needs. – donkey Nov 18 '14 at 15:55
  • @ge0rges I don't quite understand, why when space is pressed, you replace the typed word with the same value from lexicon entries? – iOS Dev Dec 12 '14 at 09:54
  • @StanleyKubrick lexicon is usually for shortcuts so thats what I implemented: user types 'lol' replace with lexicon entry 'laugh out loud'. – donkey Dec 13 '14 at 10:25
6

With regards to auto-correction, I was able to add it using link. Here's the code snippet I used from the link:

UITextChecker *checker = [[UITextChecker alloc] init];

  NSRange checkRange = NSMakeRange(0, self.txView.text.length);

  NSRange misspelledRange = [checker rangeOfMisspelledWordInString:self.txView.text 
                                                             range:checkRange
                                                        startingAt:checkRange.location
                                                              wrap:NO 
                                                          language:@"en_US"];

  NSArray *arrGuessed = [checker guessesForWordRange:misspelledRange inString:self.txView.text language:@"en_US"];

  self.txView.text = [self.txView.text stringByReplacingCharactersInRange:misspelledRange 
                                                               withString:[arrGuessed objectAtIndex:0]];

The full documentation from Apple can be found here.

jaytrixz
  • 4,059
  • 7
  • 38
  • 57
4

Although I have not personally tried creating a custom keyboard, I am basing this answer on what I can see in the documentation.

In your keyboard, create a property called entries of type [AnyObject] (Array of AnyObjects).

In your init method, or wherever you create the keyboard, call this method:

requestSupplementaryLexiconWithCompletion(completionHandler: {
    lexicon in 
    self.entries = lexicon.entries
})

I suspect that entries is actually an array of Strings or NSStrings, but it could be a dictionary or some other type. When testing this out, try figuring out what type is actually contained in entries before figuring out your logic.

I do not believe there is a way to get Apple's default autocorrect options currently. However, this WWDC talk gives insight about how they made autocorrect work in the original iPhone OS (around the 30 minute mark).

He mentions using a binary search of the array, which leads me to believe that this array is sorted. Of course, much could have changed since the first iPhone came out...

Good luck figuring out this new API!

erdekhayser
  • 6,537
  • 2
  • 37
  • 69
  • This does return an NSArray of strings in the entires when I use `self.entries.documentText` ... However the NSArray is only like 15 words total, so how can we ever use this for auto-correct when 99% of mis-spelled words aren't even in the UILexicon? :o – Albert Renshaw Jul 25 '14 at 22:14
  • I also noticed there is no way to set the `locale` of the UILexicon? – Albert Renshaw Jul 25 '14 at 22:14
2

This is the way you can actually access Lexicon words:

[self requestSupplementaryLexiconWithCompletion:^(UILexicon *receivedLexicon) {
    self.lexicon = receivedLexicon;

    for (UILexiconEntry *word in self.lexicon.entries) {
        // Text to be inserted into a text input object by a custom keyboard, corresponding to the userInput value in the same lexicon entry.
        NSLog(@"%@",word.documentText);
        // Text to match, during user input, to provide appropriate output to a text document from the documentText value in the same lexicon entry.
        NSLog(@"%@",word.userInput);
    }
}];
Carlo S
  • 953
  • 1
  • 9
  • 14
2

Rachits answer above in swift 4. Works with iOS 12

I have this helper to check wether the current string to be tested by UITextChecker is not a space

func validate(string: String?) -> Bool {
    guard let text = string,
        !text.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines).isEmpty else {
            return false
    }
    return true
}

The text checker is then in my "Spacebar", "Spacebar double tapped" & "return" methods. The example below is in my "Space" method

let textChecker = UITextChecker()
    let currentString = self.textDocumentProxy.documentContextBeforeInput
    if validate(string: currentString) {
        let charSet = NSCharacterSet.whitespacesAndNewlines
        let components = currentString?.components(separatedBy: charSet)
        let lastWord = components?.last
        let checkRange = NSMakeRange(0, lastWord?.count ?? 0)
        let misspelledRange = textChecker.rangeOfMisspelledWord(in: lastWord!, range: checkRange, startingAt: checkRange.location, wrap: false, language: "en_US")
        if misspelledRange.length != 0 {
            let guessedWord: Array = textChecker.guesses(forWordRange: misspelledRange, in: lastWord!, language: "en_US")!
            if  guessedWord.count > 0 {
                var i = 0
                while (lastWord?.length)! > i {
                    textDocumentProxy.deleteBackward()
                    i += 1
                }
                self.textDocumentProxy.insertText(guessedWord[0])

            }
        }
    }


    self.textDocumentProxy.insertText(" ")

I had to make two changes to Rachits code. First to validate the currentString since it throws an exception if you press space bar twice. And second to check if the misspelled range is not 0 because that was also throwing an exception which I am yet to figure out why. But this works for me now as is.

1

Every custom keyboard (independent of the value of its RequestsOpenAccess key) has access to a basic autocorrection lexicon through the UILexicon class. Make use of this class, along with a lexicon of your own design, to provide suggestions and autocorrections as users are entering text. The UILexicon object contains words from various sources, including:

Unpaired first names and last names from the user’s Address Book database Text shortcuts defined in the Settings > General > Keyboard > Shortcuts list A common words dictionary that includes the names of Apple products

Retro
  • 3,985
  • 2
  • 17
  • 41
  • Could you please give some method? I don't know how to do – LE SANG Jul 09 '14 at 01:46
  • it's in early stage of development but you can read more here https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UILexicon_Class/index.html – Retro Jul 09 '14 at 10:39
  • Just a little info and I still stuck. Anyway, thank you for your help! – LE SANG Jul 10 '14 at 00:56
1

In case anyone is still looking into this, I found a really nice C++ predictive text library called Presage. It seems to do a good job based on the demo but I'm having a lot of trouble trying to integrate it as a library (see my question here).

Let me know if anyone has any ideas, very interested in getting this working!

Community
  • 1
  • 1
HHHH
  • 1,197
  • 2
  • 16
  • 28
1

Actually, UILexicon is just a way to get some user-specific words that your spellchecking system should't try to fix. Probably, the most common way to use it is to fill out UITextChecker's list of ignored words.

let lexicon: UILexicon = ...
let checker: UITextChecker = ...

for entry in lexicon.entries {
    if entry.documentText == entry.userInput {
        checker.ignoreWord(entry.documentText)
    }
}

Additionally, UILexicon can be used as source of autoreplaced shortcuts like ("omw" = "On my way!"), but it is not autocorrection in terms of spelling.

Valentin Shergin
  • 7,166
  • 2
  • 50
  • 53
  • I think `if entry.documentText == entry.userInput` will almost never be true: `entry.userInput` is a shortcut and `entry.documentText` is a text to replace the shortcut. For example `userInput` is "omw" and `documentText` is "On my way!" – Ivan Mir Mar 22 '17 at 00:42
  • No, no, not always. The dictionary (lexicon) actually also contains all names from your address book (and probably something else). – Valentin Shergin Mar 22 '17 at 00:48
1

You can use below logic for AutoCorrect & it will also work in iOS 10

-(void)didClickAtAlphaNumericKeyboardKey:(NSString *)value {

    if ([value isEqualToString:@" "]) {
            UITextChecker *checker = [[UITextChecker alloc] init];
            currentString = self.textDocumentProxy.documentContextBeforeInput;
            NSCharacterSet *charSet = [NSCharacterSet whitespaceAndNewlineCharacterSet];
            NSArray *components = [currentString componentsSeparatedByCharactersInSet:charSet];
            NSString *lastWord = components.lastObject;

            NSRange checkRange = NSMakeRange(0, lastWord.length);
            NSRange misspelledRange = [checker rangeOfMisspelledWordInString:lastWord
                                                                       range:checkRange
                                                                  startingAt:checkRange.location
                                                                        wrap:NO
                                                                    language:@"en_US"];

            NSArray *guessedWord = [checker guessesForWordRange:misspelledRange inString:lastWord language:@"en_US"];

            if (guessedWord && guessedWord.count > 0) {
                for (int i = 0; lastWord.length >i ; i++) {
                    [self.textDocumentProxy deleteBackward];
                }
                [self.textDocumentProxy insertText:[guessedWord objectAtIndex:0]];
            }
    }

    [self.textDocumentProxy insertText:value];
}
Rachit
  • 814
  • 9
  • 19