Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Setting text of UITextView results in crash

I am trying to set the text of UITextView which has some illegal characters like "Unicode Character 'OBJECT REPLACEMENT CHARACTER' (U+FFFC)".
Basically, I have a UITextView. Now user taps on it and keyboard comes up. Now I use speech to text from keyboard(also known as dictation). When the dictation is processing(at that time UITextView has that special character which corresponds to the default placeholder animation), I try to set the value of text view using:

textView.text = @""

This creates following crash:

 *** Terminating app due to uncaught exception 'NSRangeException', reason: '-[__NSCFString replaceCharactersInRange:withString:]: Range or index out of bounds'

Stack trace which I got from crashlytics:

0    CoreFoundation     __exceptionPreprocess + 130
2    CoreFoundation     -[NSException initWithCoder:]
3    CoreFoundation     mutateError + 222
4    Foundation     -[NSString stringByReplacingCharactersInRange:withString:] + 134
5    UIKit      __37-[UITextInputController textInRange:]_block_invoke + 310
6    UIFoundation       -[NSTextStorage coordinateReading:] + 36
7    UIKit      -[UITextInputController textInRange:] + 232
8    UIKit      -[TIDocumentState(UITextInputAdditions) _contextAfterPosition:inDocument:] + 190
9    UIKit      -[TIDocumentState(UITextInputAdditions) initWithDocument:] + 150
10    UIKit     +[TIDocumentState(UITextInputAdditions) documentStateOfDocument:] + 52
11    UIKit     -[UIKeyboardImpl updateForChangedSelectionWithExecutionContext:] + 288
12    UIKit     -[UIKeyboardTaskQueue continueExecutionOnMainThread] + 352
13    UIKit     -[UIKeyboardTaskQueue performTask:] + 248
14    UIKit     -[UIKeyboardImpl updateForChangedSelection] + 96
15    UIKit     -[UIKeyboardImpl selectionDidChange:] + 102
16    UIFoundation      -[NSTextStorage coordinateReading:] + 36
17    UIKit     -[UITextInputController _coordinateSelectionChange:] + 100
18    UIKit     -[UITextInputController _setSelectedTextRange:] + 604
19    UIKit     -[UITextView setAttributedText:] + 392
20    UIKit     -[UITextView setText:] + 134

I have also created a sample project which demonstrates this problem. You can get this project from: https://dl.dropboxusercontent.com/u/80141854/TextViewDictationCheck.zip

Exception can be reproduced by following steps:

  1. List item
  2. Run project in xcode.
  3. Click on text view. keyboard comes up.
  4. press mic button next to space on keyboard.
  5. After sometime(4-5 seconds), press done.
  6. Now you will see a spinner moving in textview. Press send while that spinner is moving in textview.
  7. You will get exception.

I have found one method of avoiding this crash: While setting the text of UITextView, we can use following code:
I have also found one more workaround which we can use while resetting text on UITextView:

[self.textView setSelectedRange:NSMakeRange(0, [[self.textView textStorage] length])];
[self.textView insertText:@""];
[self.textView setText:@""];

But I am still not getting that why this crash happens if we only use setText: to set the text.

like image 808
Udit Agarwal Avatar asked May 15 '14 13:05

Udit Agarwal


1 Answers

The exception happens in the Foundation code, not your code. There is a race condition that causes this crash if the string is altered while the dictation is being processed. It places a placeholder into the string when you touch the microphone button, then replaces it with the text when it's finished processing. If you alter the string and remove the placeholder, it causes a crash.

The fix is to make sure you don't alter the string while dictation is being processed. You can do this by checking the current input mode primary language. It's set to dictation while dictation is in progress:

- (IBAction)sendPressed:(id)sender
{
    NSString *primaryLanguage = [self.textView textInputMode].primaryLanguage;
    if(![primaryLanguage isEqualToString:@"dictation"])
    {

        // Your original method body:
        NSString *textViewText = self.textView.text;
        textViewText = @"";
        self.textView.text = nil;
        self.textView.text = @"";

    }

}

This skips the code to empty the textview if dictation is in progress.

like image 126
DavidA Avatar answered Oct 20 '22 06:10

DavidA