Search code examples
iosobjective-cdelegatesdelegation

Cascades of delegates and hijacking delegate callbacks in Objective-C


Say I write a UITextField subclass and want to have control over the text written into it by the user. I would set the input field's delegate to be myself and implement -textField:shouldChangeCharactersInRange:replacementString:.

However, I would still want to allow whatever part of code uses me as a text field to implement the usual delegate methods. An approach for that would be to store a second delegate reference and map them like so:

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

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

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

I would then proceed to implement all UITextFieldDelegate methods and forward them to the next delegate as I wish. Obviously, I may want to modify some parameters before passing them on to the next delegate, like in -textField:shouldChangeCharactersInRange:replacementString:.
Another problem I'm thinking of is when the user's sets nextDelegate to the text field itself (for whatever reason), resulting in an infinite loop.

Is there a more elegant way to hijack delegate callbacks like in the example code I posted?


Solution

  • The problem with your approach is the overridden delegate accessor: There's no guarantee that Apple's code always uses the delegate ivar directly and does not use the getter to access the delegate. In that case it would just call through to the nextDelegate, bypassing your sneaked in self delegate.

    You might have checked that your approach works in the current implementation but this could also change in future UIKit versions.

    Is there a more elegant way to hijack delegate callbacks like in the example code I posted?

    No, I'm not aware of any elegant solutions. You could not override the delegate accessor and instead set up secondary delegate (to which you have to manually pass all delegate messages).

    To solve the actual problem of filtering text input it might be worthwhile looking into

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

    This method is implemented by UITextField (as it adopts UITextInput) and could be overridden to filter the text argument.