2

I know that there is a lot of info on the Internet about that question but I am stuck. I have app in which I need to customise my UITextFields. For example I need change border colour when edit starts or I need limitation of how many characters I can set in different text fields. I also need an image in several text fields which has to be indented on the left. To do all this I decided to make custom UITextField class (to subclass UITextField). I created new class from UITextField (which implements <UITextFieldDelegate>). In that class I use self.delegate = self (this is widely used on the internet and people say it is working) so I can implement shouldChangeCharactersInRange or textFieldShouldBeginEditing inside my custom class. My problem is that in this configuration I receive infinite loop and App restart (see my question about that). This comes from self.delegate = self. I understand that in some cases I can use observer but in that case how I can implement shouldChangeCharactersInRange inside my class? If I don't implement my class in that way and delegate my text field to my view controller. I have to implement all that methods in my view controller class which in my opinion is very ugly solution.

So my question is how properly implement UITextField subclass?

P.S. I suppose that I do it in the wrong way but I can not figure out which is the proper one.

EDIT:

Here is my code:

MyCustomTextField.h

@interface MyCustomTextField : UITextField 

@property (nonatomic) int maxSymbols;
@property (nonatomic) int leftIndent;

@end

MyCustomTextField.m

@interface MyCustomTextField () <UITextFieldDelegate>

@end

@implementation MyCustomTextField
- (id)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super initWithCoder:aDecoder]) {

        self.delegate = self;

        self.clipsToBounds = YES;
        [self setLeftViewMode:UITextFieldViewModeAlways];

        UIImageView *imageView1 = [[UIImageView alloc]
                                   initWithFrame:CGRectMake(0, 0, 37, 20)];
        imageView1.image = [UIImage imageNamed:@"otp_back"];
        self.leftView = imageView1;

    }
    return self;
}

- (CGRect) leftViewRectForBounds:(CGRect)bounds {

    CGRect textRect = [super leftViewRectForBounds:bounds];
    textRect.origin.x = 5;
    return textRect;
}

also this method for checking max length:

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

    // Restrict number of symbols in text field to "maxSymbols"
    NSUInteger oldLength = [textField.text length];
    NSUInteger replacementLength = [string length];
    NSUInteger rangeLength = range.length;

    NSUInteger newLength = oldLength - rangeLength + replacementLength;

    BOOL returnKey = [string rangeOfString: @"\n"].location != NSNotFound;

    return newLength <= (int)_maxSymbols || returnKey;
} 

When I go to the text field and start to type from virtual keyboard of the simulator I receive infinite loop and exit with BAD ACCESS. It it very strange, because if the keyboard is of type numeric or password or if I type from Mac keyboard I did not receive the issue.

Community
  • 1
  • 1
new2ios
  • 1,350
  • 2
  • 25
  • 56

3 Answers3

4

I disagree with setting the delegate inside view controllers all the time. There are a couple obvious advantages using UITextField itself as the delegate such as cleaner code when a lot of different view controllers share the same text field behavior.

In iOS 8 the self delegate reference loop seems to be "fixed". But for those targeting iOS 7 and below, we used a proxy object to bypass the loop:

InputTextProxy.h:

@interface InputTextProxy : NSObject <UITextFieldDelegate>
@property (weak, nonatomic) id<UITextFieldDelegate> proxiedDelegate;
@end

InputTextProxy.m:

#import "InputTextProxy.h"

@implementation InputTextProxy

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    return [self.proxiedDelegate textFieldShouldReturn:textField];
}

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
    return [self.proxiedDelegate textField:textField shouldChangeCharactersInRange:range replacementString:string];
}

@end

You can add more callbacks as necessary, we only use the above two.

Now create a property in your subclass MyCustomTextField.m:

@property (strong, nonatomic) InputTextProxy *inputProxy;

- (InputTextProxy *)inputProxy
{
    if (!_inputProxy)
        _inputProxy = [[InputTextProxy alloc] init];
    return _inputProxy;
}

Use this to set your delegate:

self.delegate = self.inputProxy;
self.inputProxy.proxiedDelegate = self;
Eric Chen
  • 3,562
  • 7
  • 39
  • 58
  • I agree with you, @e_x_p, but that was the only working solution (the loops happen sometimes). Also there are situations in which you need to manage additional things in `edit` events in the `ViewController`. I will try your solution for sure. – new2ios Aug 04 '15 at 16:19
  • I couldn't bear writing the same code for 15+ view controllers ... hope it will help you @new2ios – Eric Chen Aug 05 '15 at 06:58
  • Because this was only solution I already have that VCs written, @e_x_p 2. I will try to use your way in my next project or when I have time to move my App to swift. – new2ios Aug 05 '15 at 09:43
2

I wouldn't set the delegate to self within your custom UITextField, it is not really neat. What i would do is simply have a property or a method for edition that you can call in your controller when you receive delegates call.

Basically in your view controller somewhere :

- (void)viewDidLoad{
    ....
    self.textField = [[CustomTextField alloc] init];
    self.textField.delegate = self;

}

- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField{
     self.textField.editMode = YES;
}

And in your CustomTextField

-(void)setEditMode:(BOOL)edit{
    if(edit){
        self.layer.borderColor=[[UIColor redColor]CGColor];
        self.layer.borderWidth= 1.0f;
    }
}
streem
  • 9,044
  • 5
  • 30
  • 41
1

For edit starts and ends you have becomeFirstResponder and resignFirstResponder. This is the best place for that. Override and don't forget to call super. For input manipulation you have to check documentation. There are two ways, via notifications and subclass. Both offer different level of flexibility.

pronebird
  • 12,068
  • 5
  • 54
  • 82
  • I am new to the iOS development, can you provide me a link with an example of your solution? I understand that when I make edit I change FirstResponder and from that point your solution sounds fine, but I don't know how to do that. – new2ios Sep 24 '14 at 07:50
  • @new2ios I don't have particular link as it's very basic and covered in documentation, but you can probably check how it's done in FlatUIKit: https://github.com/Grouper/FlatUIKit/blob/master/Classes/ios/FUITextField.m – pronebird Sep 24 '14 at 09:43