Search code examples
iosobjective-cuitextfieldnsrange

NSRange `replaceCharactersInRange` crashing app after backspace & entry of new character


I have one textfield, & use shouldChangeCharactersInRange method to get value.

Here is my Code:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{

    NSLog(@"Pressing : %@\n", string);
    NSMutableString *text = [NSMutableString stringWithString:textField.text];

    NSLog(@"length: %lu", (unsigned long)range.length);
    NSLog(@"location: %lu\n", (unsigned long)range.location);
    NSLog(@"before text: %@", textField.text);

    [text replaceCharactersInRange:range withString:string];
    NSLog(@"After replacement , Text: %@\n\n", text);

    // My work..

    return YES;
}

I first add character 'T' to textfield, it works fine ..

2016-03-02 10:06:23.162 MyTextFieldApp[12006:4323645] Pressing : T
2016-03-02 10:06:23.163 MyTextFieldApp[12006:4323645] length: 0
2016-03-02 10:06:23.163 MyTextFieldApp[12006:4323645] location: 0
2016-03-02 10:06:23.163 MyTextFieldApp[12006:4323645] before text: 
2016-03-02 10:06:23.163 MyTextFieldApp[12006:4323645] After replacement , Text: T

Then, I backspace it to remove character, that also works fine..

2016-03-02 10:06:30.979 MyTextFieldApp[12006:4323645] Pressing : 
2016-03-02 10:06:30.980 MyTextFieldApp[12006:4323645] length: 1
2016-03-02 10:06:30.980 MyTextFieldApp[12006:4323645] location: 0
2016-03-02 10:06:30.980 MyTextFieldApp[12006:4323645] before text: T
2016-03-02 10:06:30.981 MyTextFieldApp[12006:4323645] After replacement , Text: 

But, when I am going to add new character then, it crash on replaceString..

Note: This time, Range Gives length 0 & location 1, while first time also when i am adding T to empty textfield, it gives length 0 as well as location 0.

2016-03-02 10:07:02.158 MyTextFieldApp[12006:4323645] Pressing : T
2016-03-02 10:07:02.158 MyTextFieldApp[12006:4323645] length: 0
2016-03-02 10:07:02.159 MyTextFieldApp[12006:4323645] location: 1
2016-03-02 10:07:02.159 MyTextFieldApp[12006:4323645] before text: 
2016-03-02 10:07:02.159 MyTextFieldApp[12006:4323645] *** Terminating app due to uncaught exception 'NSRangeException', reason: '-[__NSCFString replaceCharactersInRange:withString:]: Range or index out of bounds'

I can't detect where is the problem?


Solution

  • Your program is crashing because you are attempting to insert data at an invalid location in the text mutable string.

    The second time you press 'T', the value of text (retrieved from textField.text) is an empty string (literally @""). You then call replaceCharactersInRange passing a location of 1 and length 0. When you call replaceCharactersInRange with a length of 0 you are effectively saying "insert string" at position location. This function only replaces text when the length is greater than 0.

    When your code tries to insert the letter 'T' at the index 1 it can't (the first and only valid index of an empty string is 0), so the program crashes.

    Also, for what it's worth, your program shouldn't return a location of 1 and a length of 0 if the textField is empty. There must be something else going on. I wrote your sample program in Swift and got the following output:

    import UIKit
    
    class ViewController: UIViewController {
        var textField: UITextField!
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            view.backgroundColor = UIColor.blackColor()
    
            textField = UITextField(frame: CGRectMake(20, 100, 200, 20))
            textField.backgroundColor = UIColor.whiteColor()
            textField.delegate = self
    
            view.addSubview(textField)
        }
    }
    
    
    extension ViewController: UITextFieldDelegate {
        func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
            print("range.length: \(range.length), range.location: \(range.location), text: '\(textField.text!)' string: '\(string)'")
            return true
        }
    }
    

    output:

    range.length: 0, range.location: 0, text: '' string: 'T' // press T
    range.length: 1, range.location: 0, text: 'T' string: '' // press backspace
    range.length: 0, range.location: 0, text: '' string: 'T' // press T
    

    As you can see, I see the expected range of 0,0 on the second insert of T. An Objective-C program should behave similarly.