17

I'm creating a Custom Keyboard Extension for iOS 8.

I want to get the whole string that the user wrote in the text input field.

Unfortunately I stumble in to two main problems:

1) I get null in this callback:

- (void)textDidChange:(id<UITextInput>)textInput

2) I only get partial String When I call these methods:

- [self.textDocumentProxy documentContextAfterInput];
- [self.textDocumentProxy documentContextBeforeInput];

I want to get the whole string in the text input the user is currently editing what do you suggest i do?

Thanks!

dibi
  • 3,257
  • 4
  • 24
  • 31
nurnachman
  • 4,468
  • 2
  • 37
  • 40

5 Answers5

6

I found How To.

- (void)doSomething{
    dispatch_async(inputQueue, ^{
        [self getAllString];
    });
    return;
}

First Wrap Your Method With dispatch_async and your own queue.

#define DELAY_LENGTH 0.01

-(void)getAllString{
    id<UITextDocumentProxy> proxy = self.textDocumentProxy;
    NSString* before = [proxy documentContextBeforeInput];
    NSString* after = [proxy documentContextAfterInput];

    NSMutableArray* afterArray = [NSMutableArray arrayWithCapacity:10];

    while (after) {
        unsigned long afterLength = [after length];
        if(afterLength <= 0){
            break;
        }
        [afterArray addObject:after];
        [NSThread sleepForTimeInterval:DELAY_LENGTH];
        [proxy adjustTextPositionByCharacterOffset:afterLength];
        [NSThread sleepForTimeInterval:DELAY_LENGTH];
        after = [proxy documentContextAfterInput];
    }

    NSMutableArray* beforeArray = [NSMutableArray arrayWithCapacity:10];

    [NSThread sleepForTimeInterval:DELAY_LENGTH];
    before = [proxy documentContextBeforeInput];

    while (before) {
        unsigned long beforeLength = [before length];
        if (beforeLength <= 0) {
            break;
        }
        [beforeArray addObject:before];
        [NSThread sleepForTimeInterval:DELAY_LENGTH];
        [proxy adjustTextPositionByCharacterOffset:-beforeLength];
        [NSThread sleepForTimeInterval:DELAY_LENGTH];
        before = [proxy documentContextBeforeInput];
    }

    NSLog(@"afterArray: %@",[self getArrayString:afterArray reverse:false]);

    NSString* beforeString = [self getArrayString:beforeArray reverse:true];

    NSLog(@"beforArray: %@",beforeString);

    [NSThread sleepForTimeInterval:DELAY_LENGTH];
    [proxy adjustTextPositionByCharacterOffset:beforeString.length];
}

There are two utils :

@implementation NSArray (Reverse)

- (NSArray *)reversedArray {
    NSMutableArray *array = [NSMutableArray arrayWithCapacity:[self count]];
    NSEnumerator *enumerator = [self reverseObjectEnumerator];
    for (id element in enumerator) {
        [array addObject:element];
    }
    return array;
}

@end

and

-(NSString*)getArrayString : (NSArray*)array
                   reverse : (BOOL)isReverse{
    NSArray* tar = isReverse ? [array reversedArray] : array;
    NSMutableString* result = [NSMutableString stringWithCapacity:10];
    for (NSString* string in tar) {
        [result appendString:string];
    }
    return result;
}

YOU HAVE TO SLEEP LITTLE FOR EACH PROXY METHOD.

result :

TextBox

012, 345! 6789
0123. 4567! 89012 '34567890.' 123456789

Console

2015-03-18 21:04:15.034 moveCursor[39413:2692914] afterArray: 
2015-03-18 21:04:15.035 moveCursor[39413:2692914] beforArray: 012, 345! 6789
0123. 4567! 89012 '34567890.' 123456789
Jeonghan Joo
  • 225
  • 2
  • 10
3

I ran into the same problem with the documentContextAfterInput and documentContextBeforeInput strings. I think that IOS8 only returns a handful of characters on either side of the insertion point. I think this is intentional.

Hivebrain
  • 784
  • 1
  • 8
  • 11
  • 1
    thanks! does look intentional.. let me know if you find a way to access the UITextInput – nurnachman Jul 23 '14 at 09:26
  • Good observation. I believe the "handful" of characters that it returns are those that appear AFTER a typical ending punctuation mark such as a period "." or a new line space. Were you guys able to get access to the `UITextInput` ? – daspianist Sep 10 '14 at 20:36
  • It happens after period + next letter uppercase. If the letter isn't uppercase it adds the next line as well. – Warpzit Jun 30 '16 at 08:38
2

You could always have a mutable array you store all typed text in, and remove the last character when deleting(or maybe do some detection with documentContextAfterInput/BeforeInput to find the spot to delete text, then remove that portion). This is more complicated than just calling a 'giveMeWholeString' method, but it would work for most cases, especially if they were only typing and deleting with the delete key.

Milam
  • 176
  • 6
  • 1
    thank you - it's a good option. can you think what happens if the user copy-pasted text? or if the keyboard was launched in a non-empty text field? thanks :) – nurnachman Jul 27 '14 at 12:31
  • If the keyboard is launched on a non-empty text field you would most likely want to handle it where if it doesn't detect the ContextAfter/BeforeInput in the recorded string then it defaults to whatever you would try to do without this method. – Milam Jul 28 '14 at 04:13
  • I'm not sure what you're trying to do with it, so I can't really tell if this would be acceptable. If it was to help text prediction or something, it would be fine if the text prediction wasn't based on a whole sentence in that situation, but in other situations it definitely wouldn't be ideal. for the copy pasted text you could do some in depth break down of the parts of the before/afterinput string and compare it to potentially find where it was placed(or to just get all or most of the copy/pasted portion if the string is empty). Sorry it couldn't be more definite. – Milam Jul 28 '14 at 04:14
  • 1
    what i want to do is get all text and suggest synonyms and better grammar – nurnachman Jul 28 '14 at 08:06
-1

in Xcode 6.1 it seems that documentContextBeforeInput returns partial string only in simulator and works fine on the device.

Edit: ok guys here's my code:

-(void)addPeriod{
if (![self endsWithCharacter:'.' String:[[((UIInputViewController*)self.delegate).textDocumentProxy documentContextBeforeInput] stringByReplacingOccurrencesOfString:@" " withString:@""]]) {
    [((UIInputViewController*)self.delegate).textDocumentProxy deleteBackward];
    [((UIInputViewController*)self.delegate).textDocumentProxy insertText:@"."];
}
}
- (BOOL) endsWithCharacter: (unichar) c String:(NSString*)str
{
    NSLog(@"%@",str);
    NSUInteger length = [str length];
    return (length > 0) && ([str characterAtIndex: length - 1] == c);
}

and the output (on the device only) is: 2014-11-16 12:32:42.708 Keyboard[2228:393159] Thanks!howareyou?

Moataz Hossam
  • 413
  • 7
  • 20
  • please explain: which problem you're talking about? – nurnachman Nov 13 '14 at 14:07
  • 1
    Do you mean to say that no matter the length of my string, documentContextBeforeInput will always return the full string? if so, that's big news! are you sure? – nurnachman Nov 13 '14 at 18:18
  • Could you please elaborate (screenshots are awesome)? If so this is indeed big news and a huge change from XCode 6.0. – daspianist Nov 14 '14 at 14:59
  • @nurne I just downloaded XCode 6.1.1 GM to test, and it seems that `documentContextBeforeInput` is still only returning partial strings as delineated by periods or other common sentence completion marks. For example: "Thanks! How are you?" vs "Thanks, how are you?" . `documentContextBeforeInput` will only return the full string in the second sentence. – daspianist Nov 15 '14 at 23:48
  • check my edit... but now i had an evil thought..what app do u test in ur keyboard? I am testing in Notes app – Moataz Hossam Nov 16 '14 at 07:50
-2

I had the same problem and I think it has been fixed with Xcode 6 beta 4.

Just append documentContextAfterInput to documentContextBeforeInput to get the full input field text.

Carlo S
  • 953
  • 1
  • 9
  • 14
  • do you see it returns the whole strings? doesnt matter the length of string? – nurnachman Jul 30 '14 at 07:07
  • documentContextBeforeInput is returning what's before the insertion point and documentContextAfterInput what's after. So if you merge the two strings you get the whole content, no matter how long it is. – Carlo S Jul 31 '14 at 07:13
  • 2
    It **does NOT** 'see' before a newline (`\n`).. (At least in beta 5, did not check beta 6 yet) – bauerMusic Aug 22 '14 at 06:44
  • It was fixed in Beta 4 and then in Beta 5 it started happening again :o – Albert Renshaw Sep 03 '14 at 01:33
  • 1
    Correct, for new line `\n` and other punctuation marks like "!" and "." it also cannot get the text before. – daspianist Sep 10 '14 at 20:54