Search code examples
iosobjective-cuitextfieldsubclassuitextfielddelegate

Set max length of UITextField AND convert to uppercase?


I have a Client Details screen with many UITextField. I need to limit the postcodeField to a maximum of 7 characters and convert to uppercase automatically. I already have code to convert the text to uppercase, but it seems I cannot do anything else with that particular UITextField in its Delegatemethod

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

Here is what I have tried:

#define MAXLENGTH 7
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    if (textField == self.postcodeField) {
        self.postcodeField.text = [textField.text stringByReplacingCharactersInRange:range withString:[string uppercaseString]];
        return NO;
    }
    if (self.postcodeField.text.length >= MAXLENGTH && range.length == 0)
    {
        return NO;
    }
    return YES;
}

And:

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    if (textField == self.postcodeField) {
        self.postcodeField.text = [textField.text stringByReplacingCharactersInRange:range withString:[string uppercaseString]];
        return NO;
    }

    NSUInteger newLength = [textField.text length] + [string length] - range.length;
    return (newLength > 7) ? NO : YES;
}

This code does not work. I know there are many threads with various solutions to setting a maximum length, but I can't find a solution that caters for uppercase conversion too. I am quite new to iOS so I apologise if this is seen as a duplicate post. Any help is much appreciated!


Solution

  • I have decided to implement a new method for achieving what I want. My UITextField delegate method - (BOOL)textField: shouldChangeCharactersInRange: replacementString: was getting very messy as more textfields were added to the view, all of which are doing different things. So I have created a subclass to use on the desired Postcode field. I wasn't able to use the posted solution in a subclass of UITextField as it is bad to set the delegate to self within that subclass (a known issue with UITextField subclassing - see this, this, and this.). The new code is more efficient than the answer I had previously accepted, and can be widely adapted to do many things.

    The header file:

    PostcodeField.h
    
    @interface PostcodeField : UITextField
    
    - (BOOL)stringIsAcceptable:(NSString *)string inRange:(NSRange)range;
    
    @end
    

    The subclass implementation (another requirement was to only accept specified characters which has been easily implemented):

    PostcodeField.m
    
    #define ACCEPTABLE_CHARACTERS @" ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
    #define CHARACTER_LIMIT 8
    
    @implementation PostcodeField
    - (BOOL)stringIsAcceptable:(NSString *)string inRange:(NSRange)range {
        NSUInteger newLength = [self.text length] + [string length] - range.length;
        // Check text meets character limit
        if (newLength <= CHARACTER_LIMIT) {
            // Convert characters to uppercase and return acceptable characters
            NSCharacterSet *cs = [[NSCharacterSet characterSetWithCharactersInString:ACCEPTABLE_CHARACTERS] invertedSet];
            NSString *filtered = [[string componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:@""];
            [self setText:[self.text stringByReplacingCharactersInRange:range withString:[filtered uppercaseString]]];
        }
        return NO;
    }
    

    Then I set the delegate to self on the desired textfield within it's ViewController:

    self.postcodeField.delegate = self;
    

    And call it's delegate method on the ViewController:

    - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
        // Call PostcodeField subclass on Postcode textfield
        if ([textField isKindOfClass:[PostcodeField class]]) {
            return [(PostcodeField *)textField stringIsAcceptable:string inRange:range];
        } 
        return YES;
    }
    

    And of course, importing the subclass on the ViewController:

    #import "PostcodeField.h"
    

    You can set the textfield to use a subclass by navigating to the "Identity Inspector" using IB (Interface Builder) and setting the Custom Class to your subclass:

    PostcodeField subclass using IB

    By using subclasses, your UITextField delegate method can be cleaner and more efficient, and the subclass can be called on as many textfields as you like. If there are multiple textfields on that view, just follow the same process and test for each subclass within the UITextField delegate method. I hope this post will be helpful for anyone wanting to utilise subclasses on UITextFields.