Delegate cascades and intercepting delegate callbacks in Objective-C

Let's say I am writing a subclass of UITextField and I want to have control over the text written by the user. I would set an input field delegate myself and execute -textField:shouldChangeCharactersInRange:replacementString:

.

However, I still want to allow any part of the code to use me as a textbox to implement normal delegate methods. The approach for doing this would be to keep the second delegate reference and render them like this:

- (id)init {
    self = [super init];
    super.delegate = self;
    return self;
}

- (void)setDelegate:(id)delegate {
    self.nextDelegate = delegate;
}

- (id)delegate {
    return self.nextDelegate;
}

      

I then proceed to implement all of the UITextFieldDelegate methods and forward them to the next delegate as I wish. Obviously, I can change some parameters before passing them to the next delegate, for example in -textField:shouldChangeCharactersInRange:replacementString:

.
Another problem I'm thinking about is when the user sets nextDelegate

in the textbox (for whatever reason), which results in an infinite loop.

Is there a more elegant way to capture delegate callbacks like in the above code example?

+3


source to share


3 answers


The problem with your approach is the overridden accessory delegate

: there is no guarantee that Apple code will always use delegate

ivar directly instead of using it to use getter to access the delegate. In that case, it would just go to nextDelegate

, bypassing your sneaked in the delegate self

.

You may have verified that your approach works in the current implementation, but this may also change in future versions of UIKit.

Is there a more elegant way to capture delegate callbacks like in the above code example?

No, I don't know of any smart solutions. You cannot override access for a delegate and instead set an additional delegate (to which you must manually pass all delegate messages).



To solve the actual problem of filtering text input, it might be worth taking a look at

- (void)replaceRange:(UITextRange *)range withText:(NSString *)text;

      

This method is implemented UITextField

(since it accepts UITextInput

) and can be overridden to filter the argument text

.

+2


source


I think you are thinking about it correctly and the approach you mapped out will work fine (I did).

There is no rounding issue because you shouldn't be opening nextDelegate on the public interface of the subclass, so any caller won't have a way to set the loop. (You can also check in the setter that delegate != self

.

It would be best if you could avoid this altogether. For example, if you just want to change the text of a textbox when it changes, you can get a control event:

[self addTarget:self action:@selector(didChange:) forControlEvents:UIControlEventEditingChanged];

      



Then

- (void)textFieldDidChange:(id)sender {
    self.text = [self alteredText];  
}

- (NSString *)alteredText {
    // do whatever transform to user input you wish, like change user input 'a' to 'x'
    return [self.text stringByReplacingOccurrencesOfString:@"a" withString:@"x"];
}

      

This will work too, but with an odd side effect, the delegate won't see the changed text in the shouldChangeCharactersInRange:

. This can be done by making it alteredText

public and the clients of the class will call it instead of the standard getter.

+1


source


All problems with subclassing can be avoided by using a different approach to intercept delegated messages: "Delegate proxy".

The idea is to use an intermediate object (derived from NSProxy

) that either responds to the delegate's message or passes it on to the next delegate. This is basically what you did by subclassing UITextField, but instead of using a textfield object, we will use a custom object that only handles intercepting some of the delegate's messages.

These custom delegate proxies form a set of reusable building blocks that simply plug into each other to customize the behavior of any object that uses delegation.

Here is an example ( code on github ) of a delegate chain:

UITextField -> TextFilterDelegate -> SomeViewController

      

UITextField

passes delegate messages to TextFilterDelegate

, which responds to, textField:shouldChangeCharactersInRange:replacementString:

and passes other delegate messages to its own delegate (view controller).

0


source







All Articles