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?
Different UITextFieldDelegate behavior in iOS 26 with shouldChangeCharactersInRanges
 
 
Q