4
votes

In my text view I would like autocorrect to be disabled when typing words that start with "@". The reason being is because I have a tableview menu that popups and suggests usernames. If a username is selected the current text is replaced with a hyperlink. This functionality is very similar to Facebook.

Everything works great if autocorrect is disabled on my text view. However if I enable autocorrect on my text view it messes up at times because of the suggested text. Here is my code where I am attempting to change the text view's autocorrect property in the delegate protocol:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range 
replacementText:(NSString *)text 
{

    if ([text isEqualToString:@"@"]) { 

        // text starts with "@" so we disable autocorrect

        textView.autocorrectionType = UITextAutocorrectionTypeNo;

    } else if ([text isEqualToString:@" "]) { 

        // empty space so we reenable autocorrect

        textView.autocorrectionType = UITextAutocorrectionTypeYes;
    }

    return YES;
}

For whatever reason it's not working even though the autocorrect property is correctly being changed. I verified the textview autocorrect property in the debugger and it's definitely being changed but the behavior while I am typing isn't. The auto correct never disables when needed because the autocorrect menu still appears below the current text:

enter image description here

Edit: Got it working. The best solution seems to be to changing your responder to another textview that is hidden, set the original text views autocorrectType property and then reassign the original text view as the first responder. It's a little hacky but it works and doesn't cause the keyboard to jump. Also it's important to check the textViews current autocorrectionType property to prevent redundant responder assignments. Many thanks to Lyndsey for helping me get to the answer.

  • (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text { if ([text isEqualToString:@"@"] && textView.autocorrectionType != UITextAutocorrectionTypeNo) {

    // text starts with "@" so we disable autocorrect
    [self.hiddenTextView becomeFirstResponder];
    textView.autocorrectionType = UITextAutocorrectionTypeNo;
    [textView becomeFirstResponder];
    

    } else if ([text isEqualToString:@" "] && textView.autocorrectionType != UITextAutocorrectionTypeYes) {

    // empty space so we reenable autocorrect
    [self.hiddenTextView becomeFirstResponder];
    textView.autocorrectionType = UITextAutocorrectionTypeYes;
    [textView becomeFirstResponder];
    

    }

    return YES; }

3
Here's a suggestion that might work: stackoverflow.com/a/8656965/2274694Lyndsey Scott
Actually, I think I found a better answer... One sec.Lyndsey Scott

3 Answers

4
votes

I found a great answer by Engin Kurutepe about how to properly toggle autocorrect.

The solution is very simple but not documented: You can only change the properties defined in the UITextInputTraits protocol while the UITextView in question is NOT the first responder.

So in this case try:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range 
replacementText:(NSString *)text 
{

    if ([text isEqualToString:@"@"]) { 

        // text starts with "@" so we disable autocorrect
        [textView resignFirstResponder];
        textView.autocorrectionType = UITextAutocorrectionTypeNo;
        [textView becomeFirstResponder];

    } else if ([text isEqualToString:@" "]) { 

        // empty space so we reenable autocorrect
        [textView resignFirstResponder];
        textView.autocorrectionType = UITextAutocorrectionTypeYes;
        [textView becomeFirstResponder];
    }

    return YES;
}

Edit:

And although the solution is undoubtedly a bit of a "hack" as you pointed out in the comments, the reasoning Engin provided isn't and it might very well be legit even though it's not properly documented. So, if it is in fact true that "you can only change the properties defined in the UITextInputTraits protocol while the UITextView in question is NOT the first responder," then perhaps you'll be forced to use a "hack" similar to the code I posted; in which case, you can also edit the method that shifts the toolbar based on the received keyboard notifications so that your toolbar doesn't shift if the autocorrect is mid-toggle. Perhaps try using a bool like "midToggle" and set/unset it before resignFirstResponder and becomeFirstResponder in shouldChangeTextInRange so that the toolbar knows to stay stationary in those moments.

4
votes

Got it working. The best solution seems to be to changing your responder to another textview that is hidden, set the original text views autocorrectType property and then reassign the original text view as the first responder. It's a little hacky but it works and doesn't cause the keyboard to jump. Also it's important to check the textViews current autocorrectionType property to prevent redundant responder assignments. Many thanks to Lyndsey for helping me get to the answer.

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range 
replacementText:(NSString *)text 
{
    if ([text isEqualToString:@"@"] && 
        textView.autocorrectionType != UITextAutocorrectionTypeNo) { 

        // text starts with "@" so we disable autocorrect
        [self.hiddenTextView becomeFirstResponder];
        textView.autocorrectionType = UITextAutocorrectionTypeNo;
        [textView becomeFirstResponder];

    } else if ([text isEqualToString:@" "] && 
               textView.autocorrectionType != UITextAutocorrectionTypeYes) { 

        // empty space so we reenable autocorrect
        [self.hiddenTextView becomeFirstResponder];
        textView.autocorrectionType = UITextAutocorrectionTypeYes;
        [textView becomeFirstResponder];
    }

    return YES;
}
0
votes

A less hacky way to solve this is to check if the string you are entering is the string you chose. You can set a variable to hold the string that you selected in didSelectRowInIndexPath. Then, in the textView's delegate method shouldChangeTextInRange check if the replacementText matches the string you chose in didSelectRowAtIndexPath. If it doesn't match then return false.