iosiphone-softkeyboard

Get the current text inside of a textfield for IOS custom keyboard


I am developing a IOS custom keyboard. I was wondering if there was a way to fetch the current text inside of the text field and how it would work.

For example, we can use textDocumentProxy.hasText() to see if the textfield has text inside but I want to know the exact string that is inside the textfield.


Solution

  • The closest things would be textDocumentProxy.documentContextBeforeInput and textDocumentProxy.documentContextAfterInput. These will respect sentences and such, which means if the value is a paragraph, you will only get the current sentence. Users have been known to retrieve the entire string by repositioning the cursor multiple times until everything is retrieved.

    Of course, you generally do not have to worry about this if the field expects a single value like a username, email, id number, etc. Combining the values of both before and after input contexts should suffice.

    Sample Code

    For the single phrase value, you would do:

    let value = (textDocumentProxy.documentContextBeforeInput ?? "") + (textDocumentProxy.documentContextAfterInput ?? "")
    

    For values that might contain sentence ending punctuation, it will be a little more complicated as you need to run it on a separate thread. Because of this, and the fact that you have to move the input cursor to get the full text, the cursor will visibly move. It is also unknown whether this will be accepted into the AppStore (after all, Apple probably did not add an easy way to get the full text on purpose in order to prevent official custom keyboards from invading a user's privacy).

    Note: the below code is based off of this Stack Overflow answer except modified for Swift, removed unnecessary sleeps, uses strings with no custom categories, and uses a more efficient movement process.

    func foo() {
        dispatch_async(dispatch_queue_create("com.example.test", DISPATCH_QUEUE_SERIAL)) { () -> Void in
            let string = self.fullDocumentContext()
        }
    }
    
    func fullDocumentContext() {
        let textDocumentProxy = self.textDocumentProxy
    
        var before = textDocumentProxy.documentContextBeforeInput
    
        var completePriorString = "";
    
        // Grab everything before the cursor
        while (before != nil && !before!.isEmpty) {
            completePriorString = before! + completePriorString
    
            let length = before!.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)
    
            textDocumentProxy.adjustTextPositionByCharacterOffset(-length)
            NSThread.sleepForTimeInterval(0.01)
            before = textDocumentProxy.documentContextBeforeInput
        }
    
        // Move the cursor back to the original position
        self.textDocumentProxy.adjustTextPositionByCharacterOffset(completePriorString.characters.count)
        NSThread.sleepForTimeInterval(0.01)
    
        var after = textDocumentProxy.documentContextAfterInput
    
        var completeAfterString = "";
    
        // Grab everything after the cursor
        while (after != nil && !after!.isEmpty) {
            completeAfterString += after!
    
            let length = after!.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)
    
            textDocumentProxy.adjustTextPositionByCharacterOffset(length)
            NSThread.sleepForTimeInterval(0.01)
            after = textDocumentProxy.documentContextAfterInput
        }
    
        // Go back to the original cursor position
        self.textDocumentProxy.adjustTextPositionByCharacterOffset(-(completeAfterString.characters.count))
    
        let completeString = completePriorString + completeAfterString
    
        print(completeString)
    
        return completeString
    }