3

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];
}
Community
  • 1
  • 1
rosshump
  • 370
  • 1
  • 4
  • 21
  • I think this answer should describe it pretty well: http://stackoverflow.com/a/8049532/2708650 My advice to you is to simply never call `self.delegate = self` on any class for any reason. The reason to have a delegate is because the class itself is not capable of handling the state. If you are assigning `self` as the delegate, it means your class *does* handle the state. – Ian MacDonald Oct 21 '14 at 14:06
  • Thanks for the link, but I have read that post several times and still cannot find a solution. I could add the `textField shouldChangeCharactersInRange:` method to my class and assign `self.myTextField.delegate = self;` within it and it works fine, though some of my `ViewController`s have many `UITextFields` which all do different things. Rather than have an untidy `textField shouldChangeCharactersInRange:` method, surely it would be more efficient to call different subclasses on different textfields? Please see my updated post as an example. – rosshump Oct 21 '14 at 14:17

1 Answers1

1

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;
}
Ian MacDonald
  • 13,472
  • 2
  • 30
  • 51
  • I'm a bit confused with your answer, do you mean write this code on my `ViewController` class which holds the `textfields`? I don't want to call the `textField delegate` method on the actual `ViewController` class, please see my updated post, as I have already done this and the method ends up getting messy and hard to debug when there are multiple `textfields` doing different things. Or do you mean amend my `subclass` with your code? Sorry about this, I'm still just learning about subclasses and delegates, thanks. – rosshump Oct 21 '14 at 14:43
  • It doesn't have to be as messy as that. I'll further expand my answer for you. – Ian MacDonald Oct 21 '14 at 14:52
  • Sorry for the late reply, I have just implemented your code and it works perfectly! This is a much more elegant solution than what I was using, and now I can create as many subclasses as needed and call them on the `shouldChangeCharactersInRange:` `delegate` method on my `ViewController` class. I really like this solution, thanks a lot for spending your time to help me :) – rosshump Oct 22 '14 at 08:16