diff --git a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm index 8db8703fb665..9633ab9947a5 100644 --- a/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm +++ b/packages/react-native/Libraries/Text/TextInput/RCTBackedTextInputDelegateAdapter.mm @@ -96,6 +96,15 @@ - (BOOL)textField:(__unused UITextField *)textField return YES; } + // Clamp the range to the current text length to avoid NSRangeException. + NSUInteger textLength = _backedTextInputView.attributedText.length; + if (range.location > textLength) { + return NO; + } + if (range.location + range.length > textLength) { + range = NSMakeRange(range.location, textLength - range.location); + } + NSMutableAttributedString *attributedString = [_backedTextInputView.attributedText mutableCopy]; [attributedString replaceCharactersInRange:range withString:newText]; [_backedTextInputView setAttributedText:[attributedString copy]]; @@ -292,8 +301,13 @@ - (BOOL)textView:(__unused UITextView *)textView shouldChangeTextInRange:(NSRang return NO; } - if (range.location + range.length > _backedTextInputView.text.length) { - range = NSMakeRange(range.location, _backedTextInputView.text.length - range.location); + // Clamp the range to the current text length to avoid NSRangeException. + NSUInteger textLength = _backedTextInputView.attributedText.length; + if (range.location > textLength) { + return NO; + } + if (range.location + range.length > textLength) { + range = NSMakeRange(range.location, textLength - range.location); } else if ([newText isEqualToString:text]) { _textDidChangeIsComing = YES; return YES;