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?
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.
I think you're thinking about this correctly, and the approach you outlined will work fine (I've done it).
There's no circularity issue because you shouldn't expose nextDelegate in the subclass's public interface, so no caller will have the chance to setup a cycle. (You could also test in the setter that delegate != self.
It would be better, though, if you could avoid this altogether. For example, if you just want to tweak the text field text as it changes, you can get the 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 as well, but with the odd side effect that the delegate won't see the alteredText in shouldChangeCharactersInRange:. That's fixable by making alteredText public and having the class customers call it instead of the standard getter.
All of the problems with subclassing can be avoided by using a different approach of intercepting delegate messages: A "delegate proxy".
The idea is to use an intermediate object (derived from NSProxy) that either responds to a delegate message or passes it along to the next delegate. It's basically what you did by subclassing the UITextField but instead of using the text field object we'll use a custom object that handles only the interception of some delegate messages.
These customized delegate proxys form a set of reusable building blocks which are simply plugged into each other to customize the behavior of any object that uses delegation.
Here's an example (code on github) of a chain of delegates:
UITextField -> TextFilterDelegate -> SomeViewController
The UITextField passes delegate messages to TextFilterDelegate which responds to textField:shouldChangeCharactersInRange:replacementString: and passes other delegate messages on to its own delegate (the view controller).
Related
I am working with keyboard resign features in iPhone app development. I would like to know why
self.textField.delegate = self
needs to be included into the viewDidLoad of a viewController. I have tried to find reasons of this but no explanation has been clear so far.
A few points
The reason you need to set the delegate is because without it the view doesn't know about the view controller. So it wouldn't know about your method textFieldDidEndEditing and it would never be called.
That is the basic premise of delegate, you are telling that object, "here is an object that I want you to call methods on"
It doesn't have to be set in viewDidLoad - but it's often the most convient place to set up delegates for views.
The delegate doesn't have to be the view controller (self), in your case it's the simplest way, but with a UITableView its common to have another class be the delegate so that all the logic isn't in one place and so it can be changed.
The UITextFieldDelegate protocol defines methods that you use to manage the editing and validation of text in a UITextField object. All of the methods of this protocol are optional.
A text field calls the methods of its delegate in response to important changes. You use these methods to validate text that was typed by the user, to respond to specific interactions with the keyboard, and to control the overall editing process. Editing begins shortly before the text field becomes the first responder and displays the keyboard (or its assigned input view).
From more info. check apple doc.
Its not necessary to use self.textField.delegate = self if you don't want to manage the editing and validation of text in a UITextField object as all the methods of UITextFieldDelegate is optional.
For your other questions like what does .delegate = self do??
When you "set the delegate," what you are doing is saying where you want the messages to go.
Hence,
blah.delegate = amazingPlace will send the messages to "amazingPlace".
blah.delegate = somewhereElse will send the messages to "somewhereElse".
blah.delegate = self will send the messages to you.
... check this source link for details
Delegates are key concepts in iOS development so I'd suggest taking a good look at the documentation for them. It can be particularly useful to create your own custom delegates in certain situations too so understanding and using them in the right places can really help improve the structure of your projects.
There are a couple of key reasons for using them. Firstly, they allow safe communication between classes. In your example, the textField object that you're using is communicating back to your view controller. This is why you need to set your view controller as its delegate. Otherwise the text field doesn't have a delegate object (your view controller) to communicate with. The text field fires certain methods at certain times, such as textFieldDidBeginEditing, and calls these on its delegate object if it has one. When you register your view controller as the text view's delegate you can tap into these callbacks.
The other benefit is that delegates allow you to separate concerns and encapsulate or abstract responsibilities. It might be that the main concern for the text view is how to handle text in its view but not necessarily what to do when has been entered, or when the return button in the keyboard is pressed, or how to validate text that has been input. It's better that these tasks are handed over to something else, such as a delegate (in Obj-C parlance), and that is why in your example you have to register one class as the delegate for another.
As stated before, UITextfield delegation allows you to control events on your textfield.
You ll have the ability to edit functions like
textFieldShoulEndEditing
or
textFieldDidEndEditing
in order to add custom rules, for example : text validation.
Take a look at Apple doc.
If you don't need it, you can delete this line and UITextfieldDelegate on your class declaration.
You need to either set the delegate of a UITextField in code with self.textField.delegate = self
or make your viewcontroller (or any other class) a delegate with class MyViewController: UITextFieldDelegate and set the delegate of the UITextField in the storyboard by control dragging from the textfield to the viewController.
I have a UITableView combined with a fetched Results Controller. I deployed controller will change content method, and set the delegate of the frc to self.
But in a function I want to nil out it's delegate so that the will change content delegate method won't be called, and change delegate to self again after some operations to make sure other methods work right, just like this:
-(void)function
{
self.frc.delegate = nil;
for (id obj in self.frc.fetchedObjects) {
if ([obj isKindOfClass:[MultiValue class]]) {
MultiValue * multiValue = (MultiValue *)obj;
multiValue.isSelected = [NSNumber numberWithBool:YES];
}
}
self.frc.delegate = self;
}
The problem is, the delegate method (controllerWillChangeContent) will be called after the function method, so after I set self.frc.delegate to self, the delegate method will still be called.
How to solve this? Many thanks.
First of all:
Delegating is a way to customize the behavior of objects. It has similarities with subclassing on a per-instance basis. Would you change the hierarchy of an instance's class while the object is living? (Yes, you would do in some situations – very rarely.)
So the delegate of an object is not a "state" of the object, you should change. It is something like the "kind" of an object. You should recheck your whole approach.
Second of all:
The reason is that all changes are collected and the delegate message is sent afterwards, when you reset the delegate. This behavior is better in most situations. From the docs:
Rather than responding to changes individually (as illustrated in
Typical Use), you could just implement controllerDidChangeContent:
(which is sent to the delegate when all pending changes have been
processed) to reload the table view.
https://developer.apple.com/library/ios/documentation/CoreData/Reference/NSFetchedResultsControllerDelegate_Protocol/index.html
To your Q:
You should recheck, why you do not want to be informed about changes in a specific situation while you want to be informed in other situations. This looks like a structural smell. If you are really in such a situation, you should recheck this again. If you are still in such a situation, you can set a flag inside the delegate that signals to ignore a change.
I'm building a custom keyboard
and I'm implementing the following delegate methods in my InputViewController.
But I always get _textInput = nil_
- (void)textWillChange:(id<UITextInput>)textInput
- (void)textDidChange:(id<UITextInput>)textInput
- (void) selectionWillChange:(id<UITextInput>)textInput
- (void) selectionDidChange:(id<UITextInput>)textInput
Does anybody know how to fix it?
Is it nil for a reason?
Do I need to implement something by myself?
Good question. But it seems that UITextInputDelegate is not a protocol that you implement.
From Apple Docs titled Lower Level Text-Handling Technologies:
When changes occur in the text view due to external reasons—that is,
they aren't caused by calls from the text input system—the UITextInput
object should send textWillChange:, textDidChange:,
selectionWillChange:, and selectionDidChange: messages to the input
delegate (which it holds a reference to). For example, when users tap
a text view and you set the range of selected text to place the
insertion point under the finger, you would send selectionWillChange:
before you change the selected range, and you send selectionDidChange:
after you change the range.
And from the docs on UITextInputDelegate:
The UIKit provides a private text input delegate, which it assigns at
runtime to the inputDelegate property of the object whose class adopts
the UITextInput protocol.
The implication of the above is that we don't implement these delegate methods; we use them to inform the inputDelegate that you have changed your text or selection via means other than keyboard input.
Here is an example method that illustrates this:
- (void)delete:(id)sender;
{
if (selection && ![selection isEmpty]) {
[inputDelegate textWillChange:self];
[inputDelegate selectionWillChange:self];
[self replaceRange:selection withText:#""];
[inputDelegate selectionDidChange:self];
[inputDelegate textDidChange:self];
}
}
Sample code with more examples here.
Subclass of UIView
I have a subclass MyView of UIView.
This subclass has a #property UIView * realView.
What I want to do
Whenever a message is sent to MyView, I want to "forward it" to self.realView, excepted for few messages.
For instance, in the implementation of MyView, I would have this override:
- (void)setBackgroundColor:(UIColor *)color
{
[self.realView setBackgroundColor:color] ;
}
Instead of overriding explicitly all the methods, can I do it automatically, at the runtime?
Exceptions
For some methods, I want to have an explicit control. For instance:
- (void)setFrame:(CGRect)frame
{
/* do stuff */
[super setFrame:frame] ;
}
Instead of overriding explicitly all the methods, can I do it automatically, at the runtime?
You implement the -forwardInvocation: method to send any unrecognized messages to the other object. -forwardInvocation is called whenever an object doesn't implement the selector that's passed to it as a sort of second chance to handle a message. You can override it to send the message to another object (which is pretty much what NSProxy does), log the messages, etc.
As #cobbal points out below, -forwardInvocation will help you with methods not implemented in your superview, but it won't handle methods that are implemented int the superview because your MyView inherits implementations of those. For example, if you want to use a UIView as a proxy for a UIButton, all the methods specific to UIButton can be handled by -forwardInvocation:, but those defined by UIView cannot. In order to get a behavior other than the inherited method, you will of course need to override. In some situations you can get around that by deriving MyView from NSObject or UIResponder instead of from UIView, thus avoiding the inherited UIView implementations, but if MyView needs to be a real view you're stuck with overriding each method.
If you think about it, it's hard to imagine how your goal could be met without explicitly overriding each inherited method. You say that you only want to forward most messages, but how can the poor runtime tell which ones you do want to forward and which ones you don't? All it can do is look for a method for the given selector and call it if it finds one, or take some action (like calling -forwardInvocation:) if it doesn't.
Update: #robmayoff points out -forwardingTargetForSelector:, which didn't occur to me but is probably a better solution in your case. It still doesn't handle the situation where you need to redirect methods that you inherit from a superclass, though.
It's entirely possible.
First you need WZProtocolIntercepter. Then use the intercepter as the normal UIView:
WZProtocolInterceptor* fakeView = [[WZProtocolInterceptor alloc]
initWithInterceptedProtocol:#protocol(TheMethodsForTheMiddleManToHandle)];
fakeView.receiver = self.realView;
fakeView.middleMan = self;
[someViewController.view addSubview:fakeView];
Put the methods you want to control in TheMethodsForTheMiddleManToHandle:
#protocol TheMethodsForTheMiddleManToHandle
- (void)setFrame:(CGRect)frame;
#end
Still I couldn't understand completely how these delegate methods are getting called.
I have UIViewController,UITextFieldDelegate in one class which will call its delegate methods without specifying like textField.delegate = self;
But for some different purposes like UIWebViewDelegate we are supposed to enter like webView.delegate = self; and it seems like it is calling its delegate methods. Perfect.
But now I am facing a problem. I am using CLLocationManagerDelegate and also CALayer in same class. For both I am giving location.delegate =self; and layer.delegate =self; At some point both are conflicting each other and only one of the thing is working either CLLocationManagerDelegate or CALayer. The other thing is getting stopped. I don't why it so happens like this? Any reason? How can we overcome this. Even I planned to use some other frameWork, say UIWebView . I will face same problem for those delegate methods also. Can you tell me why it is working in that way ?
The classes that will call its delegates without you specifying them have default implementations, which means that they already know what to do, and will only change their behavior if you override these methods.
Setting 2 or more classes to the same delegate should not interfere with each other (unless for a very weird reason in where the method is named the same in both custom classes).
Your problem is most likely the fact that you havent implemented those methods or are using those classes wrong.
For example, Location manager requires you to create an instance, configure it and START running updates. The most common method for a delegate of this type is the "did update location" (or something like that). Which you have to implement if you want to be informed of every time a new location is received. Otherwise you have to read the location manually whenever you desire.
As a suggestion, every time you set a delegate for an object, you have to do the object.delegate = self; thing. And you probably noticed that you will get a warning until you specify in the header that it conforms to that protocol: for example.
Just control click the UITextFieldDelegate word.
Look for the methods under #required, THOSE you have to always implement. the #optional have default implementations so unless you wanna change the behavior its not needed to implement them.