Search code examples
iosobjective-cdelegatessubclassuitextfielddelegate

Subclassing UITextField with self.delegate causes app to freeze, CPU spikes to 100%


I have a subclass of UITextField which sets self.delegate = self. The subclass is used to prevent special characters being entered into a UITextField. At first it works fine, but after a few keys are pressed the CPU spikes to 100% and freezes the app. There is no crash log in Xcode because the app never actually crashes, it just stays frozen until I stop it. After some research, I have determined that the problem is setting the delegate to self - apparently I should make a separate delegate for UITextField? I have searched online but cannot find anything useful on how to do this.

My AcceptedCharacters subclass:

AcceptedCharacters.h

#import <UIKit/UIKit.h>

@interface AcceptedCharacters : UITextField <UITextFieldDelegate>

@end

And

AcceptedCharacters.m

#import "AcceptedCharacters.h"
#define ACCEPTABLE_CHARACTERS @" ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_."

@implementation AcceptedCharacters

- (void)awakeFromNib
{
    [super awakeFromNib];

    if (self) {
        self.delegate = self;
    }
}

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    //NSLog(@"AcceptedCharacters");
    // Restrict special characters
    NSCharacterSet *cs = [[NSCharacterSet characterSetWithCharactersInString:ACCEPTABLE_CHARACTERS] invertedSet];
    NSString *filtered = [[string componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:@""];

    return [string isEqualToString:filtered];
}

@end

Xcode CPU Report

Similar questions I have found on stack overflow:

Application freezes after editing custom UITextField

UITextField delegate jumping to 100% CPU usage and crashing upon using keyboard shortcut

Why does UITextField lock up when setting itself to delegate

I have read through the solutions but they are quite vague for a beginner. I would greatly appreciate someone to amend my code below or even point me in the direction of a tutorial or documentation? Thanks in advance.

EDIT:

I have tested this code on one of my ViewControllers and it works fine when conforming to the UITextFieldDelegate protocol and when my textfield delegates are set to self (see snippet below).

In my viewDidLoad method:

self.forenameField.delegate = self;
self.surnameField.delegate = self;
self.addLine1Field.delegate = self;
self.addLine2Field.delegate = self;
self.addLine3Field.delegate = self;
self.addLine4Field.delegate = self;
self.addLine5Field.delegate = self;
self.postcodeField.delegate = self;
self.telLandField.delegate = self;
self.telMobField.delegate = self;
self.emailField.delegate = self;
self.dobField.delegate = self;
self.niNumField.delegate = self;

I would rather create several subclasses of UITextField and call them on specific textfields rather than have a messy method like the one below.

#define ACCEPTABLE_CHARACTERS @" ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_."
#define NUMBERS_ONLY @"1234567890"
#define CHARACTER_LIMIT 11
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
    // Restrict special characters
    NSCharacterSet *cs = [[NSCharacterSet characterSetWithCharactersInString:ACCEPTABLE_CHARACTERS] invertedSet];
    NSString *filtered = [[string componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:@""];

    // Convert postcode field characters to uppercase
    if (textField == self.postcodeField) {
        [textField setText:[textField.text stringByReplacingCharactersInRange:range withString:[string uppercaseString]]];
        return NO;
    }

    // Set max character limit on tel fields
    if (textField == self.telMobField || textField == self.telLandField) {
        NSUInteger newLength = [textField.text length] + [string length] - range.length;
        NSCharacterSet *cs = [[NSCharacterSet characterSetWithCharactersInString:NUMBERS_ONLY] invertedSet];
        NSString *filtered = [[string componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:@""];
        return (([string isEqualToString:filtered])&&(newLength <= CHARACTER_LIMIT));
    }
    //return YES;
    return [string isEqualToString:filtered];
}

Solution

  • Try this instead:

    @interface MyTextField : UITextField
    - (BOOL)stringIsAcceptable:(NSString *)string inRange:(NSRange)range;
    @end
    @implementation MyTextField
    - (BOOL)stringIsAcceptable:(NSString *)string inRange:(NSRange)range {
      NSCharacterSet *cs = [[NSCharacterSet characterSetWithCharactersInString:ACCEPTABLE_CHARACTERS] invertedSet];
      NSString *filtered = [[string componentsSeparatedByCharactersInSet:cs] componentsJoinedByString:@""];
    
      return [string isEqualToString:filtered];
    }
    @end
    
    @interface PostalCodeTextField : MyTextField
    @end
    @implementation PostalCodeTextField
    - (BOOL)stringIsAcceptable:(NSString *)string inRange:(NSRange)range {
      [self setText:[self.text stringByReplacingCharactersInRange:range withString:[string uppercaseString]]];
      return NO;
    }
    @end
    
    .. more subclasses
    

    Then assign your view controller as the delegate and in your view controller:

    - (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
      if ([textField isKindOfClass:[MyTextField class]]) {
        return [(MyTextField *)textField stringIsAcceptable:string inRange:range];
      }
      return YES;
    }