Different UITextFieldDelegate behavior in iOS 26 with shouldChangeCharactersInRanges

Scenario: Typing Chinese Zhuyin “ㄨㄤ” and then selecting the candidate word “王”.

  • On iOS 18, the delegate (textField:shouldChangeCharactersInRange:replacementString:) is called with:
    • range: {0, 2}
    • replacementString: "王"
  • On iOS 26, the delegate (textField:shouldChangeCharactersInRanges:replacementString:) instead provides:
    • ranges: [{2, 0}]
    • replacementString: "王"

This results in inconsistent text input handling compared to earlier system versions.

Implement: Limit user input to a maximum of 100 Chinese characters.

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    if ([textField markedTextRange]) {
        return YES;
    }
    NSString *changedString = [textField.text stringByReplacingCharactersInRange:range withString:string];
    if (changedString.length > 100) {
        return NO;
    }
    return YES;
}

Questions:

  1. Is this an intentional change in iOS 26?
  2. If intentional, what is the recommended way to handle such cases in order to support both iOS 18 and iOS 26 consistently?

Duplicate question, please redirect to https://developer.apple.com/forums/thread/801013 for unified discussion.

This is still a problem in iOS 26.0.1. The behavior is significantly different than on iOS 18 and below.

Using the new iOS 26 method - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRanges:(NSArray<NSValue *> *)ranges replacementString:(NSString *)string API_AVAILABLE(ios(26.0), tvos(26.0), visionos(26.0), watchos(26.0));

Does NOT resolve the issue as it reports the same WRONG values.

Different UITextFieldDelegate behavior in iOS 26 with shouldChangeCharactersInRanges
 
 
Q