I understand how to use delegation with iOS objects that already exist. For example, if I create an instance of a UITableView, and my view controller conforms to the UITableView delegate, I can implement the various methods of the UITableView delegate. My newly create table can receive notifications, for example, when didSelectRowAtIndexPath is called.
My question is why did my table get this particular delegate callback? My understanding is that the delegate is just a list of methods with no implementation. It seems to me there must be a lot more going on. What is really going on "behind the scenes"?
Image if I were to rename all the delegate methods to the following:
- mysteryMethod1
- mysteryMethod2
- mysteryMethod3... Etc
One of these methods is responsible for setting the height of a row at a particular index. Another one these methods will be responsible for editing a particular row.
Everything I read about delegation says the delegator makes a contract with the delegate. The delegate promises to implement the methods. When it does, somehow everything is wired up correctly and everything magically works. What is the magic that I'm not seeing?
I think that in order to know how delegates actually work you should create your own custom delegate first, that way you will see that there is no magic under the hood, you probably can't see the implementation of the actual apple build in delegate methods but I assure you that there is a lot of logic implemented in those but it's just not available for privacy reasons I assume.
When you create your custom delegate let's say for example...
You have Class A and in this class, you start by creating a protocol
protocol ClassADelegate: class {
func changeBackgroundColor(_ color: UIColor?)
}
In this class you have a delegate property like this.
weak var delegate: ClassADelegate?
Let's say that this class is a Viewcontroller and you have an IBACtion on it like a UIbutton, and your goal is that when you tap that button another ViewController in your app change its background color to blue. Inside this action in Class A you do this...
func someAction() {
delegate?.changeBackgroundColor(.blue)
}
,
Let's say that the "magic" happens here in class A, by the way if you are thinking in delegates using UITableview think that UItableView is class A.
ok so now you have Class B that is where you want to change the color right?
Well now class B needs to conform to the protocol like this, just like you also conform to the protocol UITableViewDelegate etc.
class ClassB: UIViewController, ClassADelegate {
}
Now think of the word delegate for a second and think what that means, you are just delegating responsibility to somebody else, right? and yes, in this case, ClassB is going to be the delegated, for that we need to have an instance of Class A in class B just to have access to it's delegate property.
let classa = ClassA()
classa.delegate = self
the final step is just to call the method of the protocol like this..
func changeBackgroundColor(_ color: UIColor?) {
view.backgroundColor = color
}
To conclude if yo see this method in your class but you don't have access to the implementation of the protocol, you will ask yourself "where does this magic color coming from??" but as you saw it just comes from another class where a protocol belongs to, hope this helps.
Related
I have tried to make a location autocomplete text view class by subclassing UITextField and use Google Place Autocomplete API. This works great, but I have a design error due to the implementation. To observe when the user types text, I set the UITextFieldDelegate to self in the custom subclass and track changes to the typed text in textView:shouldChangeTextInRange:replacementText:. This works, but here is the design error: If someone now wants to check what is typed into the custom subclass by setting the delegate to something new, the delegate of my class is not set to the object of the class itself anymore. Now the custom class is useless. Is there any way to either get the text as it is typed without the delegate, prevent the delegate from being changed, or in any other way fix my problem?
A few options I have though about that could work, but in a bad way:
Check regularly what the text property is: Should be obvious why busy waiting is a stupid idea
Override the delegate property and set it to private: Not sure if this will even work, but if it did, the class is no longer a proper subclass of UITextField and all delegate methods are unavailable when implementing my subclass.
Provide a new delegate for further use of the delegate: Allows someone to get the same things as the UITextFieldDelegate provides, but it still messes up the documentation and proper implementation of UITextField
Delegates in UIKit I normally one to one connections. Which can cause the problem you have described.
If you want multiple delegates of a UITextField I would derive a class from UITextField for example MYTextField and add a method to addDelegate and removeDelegate that maintains a list of delegates. The sent the MYTextField's delegate to itself and broadcast any delegate method to all listeners in the delegate array.
this post shows example code on how do maintain a list of multiple delegates.
Delegation to multiple objects
While trying to implement an extension for UIViewController I realise that there is no normal way, or is not allowed to override this functions (even when they are available for UICollectionViewController and UITableViewController):
extension UIViewController{
public override func viewWillAppear(){
super.viewWillAppear()
//do some stuff
}
}
I realise that there is no normal way, or is not allowed to override this functions (even when they are available for UICollectionViewController and UITableViewController):
viewDidLoad
viewWillLoad
viewWillAppear
viewDidAppear
There is some way to do this? I would like to have some implementation there and working for every UIViewController on my app... All in just one place.
Please, note that I don't want to make a new class subclassing
UIViewController, overriding those methods and making my controller to
extend it. This is the obvious and simplest solution, but this do not satisfy what I'm trying to do.
I'm using swift 1.2 in XCode 6.3
What you are trying to do is similar to what done by this code:
class MyClass {
func myFunc() {}
}
extension MyClass {
override func myFunc() {}
}
The 4 methods that you are trying to override are defined in UIViewController, and not to one of its superclasses. And you can't override a method which is defined in the same class.
Update
I can think of 2 different ways to solve the problem - the first is the one you don't want (subclassing UIViewController).
The other one is method swizzling - I never used it so I don't want to provide you inaccurate info. Maybe it's worth reading this article by Nate Cook, which incidentally is showing an example of replacing viewWillAppear.
I am creating a Swift project and I want to define a specific protocol that enforces other components to implement a animate method:
protocol AnimatableBehavior {
#IBAction func animate()
}
The problem is I want this method to be an IBAction, but I get this error from XCode:
Only instance methods can be declared 'IBAction'
My question is, how would you implement such a thing?
I have considered:
Remove #IBAction, but then I need to remember adding it in every class that implements. Not very elegant and error prone.
Create a base class instead of protocol, but then I am enforcing all components to subclass my base class instead of their own choice ones, so it is not a valid option.
Any other ideas?
EDIT: Response to comments below.
The idea of the IBAction on the protocol is because in the project there will be many different devs implementing small UI components, all of which have the animate method. The components can be added programatically or by Interface Builder and it is very convenient that they are always IBAction because I plan to compose them from IB files to simplify the View Controllers to the maximum extent (and this is clearly a View only task).
Therefore, the solution proposed below of adding a method in the controller that just calls the animate of the component is not good because it is redundant code and makes your Controller more dependent on your View.
The idea of letting the dev to remember adding the IBAction keyword on the method is workable, but as I said it is error prone (and by that I mean that there will be some forgetting about it), and I want to make sure that this is always accessible from IB. It also adds extra cognitive load, because I will need to document this lack of IBAction on the protocol and request the implementor to add it manually.
I know is not the common way of working in iOS and UIKit, but that was why I posted the question, maybe someone has an alternative idea.
It doesn't make any sense to have an #IBAction in a protocol. #IBAction is nothing more than a keyword for Interface Builder to have a hook when you're control+dragging from Interface Builder to your actual source code.
This is just a simple misunderstanding of what #IBAction actually is and does.
A method does not have to be marked as #IBAction in order for it to be the target of a UI element's actions. You programmatically hook up any method to any action using the addTarget set of methods that UI elements have. The method does not have to be marked as an #IBAction to do this.
Regardless of whether or not a protocol defines a method as #IBAction, the class conforming to the protocol can add it (and still be conforming to the protocol.
protocol FooProtocol {
func doSomething()
}
class ViewControllerA: UIViewController, FooProtocol {
#IBAction func doSomething() {
// do something
}
}
class ViewControllerB: UIViewController, FooProtocol {
func doSomething() {
// do something
}
}
Both of these view controller subclasses conform to the protocol, and having #IBAction there is ONLY necessary if you intend to hook up an action from interface builder!
Ultimately, whatever you're trying to do, if you think an #IBAction is necessary in your protocol, I think you're taking the wrong approach to something. It's hard to say what the right approach would be without knowing more details about what you're actually doing, but it never makes sense for #IBAction to belong in a protocol.
To me, it seems like the methods your protocol enforces shouldn't at all be tied to #IBAction methods. Instead, whatever user interaction should trigger the animation, should in turn call the animate method. For example, if we weren't talking about the protocol, my recommendation would be this sort of set up:
class ViewController: UIViewController {
#IBAction func buttonThatStartsAnimation {
self.animate()
}
func animate {
// code that does all the animation
}
}
So, with the protocol, we should take the same seperation of duties between the method that's actually initiating the animation code (which in the case of protocols, this is obviously some other outside class), and the animate method should only ever handle doing the relevant animations.
Importantly, just as a general rule, you shouldn't be directly referring to your #IBAction methods or your #IBOutlet variables directly from outside the class which defines them.
I totally agree with OP, although until Swift 3.1 you can't really declare anything as #IBOutlet, #IBAction, #objc etc in a protocol. As a workaround, I chose to build something based on pod 'ActionKit' and wrote something like:
protocol RequiresAnimation {
var animateButton: UIButton! { get }
func enableAnimateButton()
func actionAnimate()
}
extension RequiresAnimation where Self: UIViewController {
func enableAnimateButton() {
animateButton.addControlEvent(.touchUpInside) {
self.actionAnimate()
}
}
func actionAnimate() {
// animate here
}
}
And make your view controller:
class MyViewController: UIViewController, RequiresAnimation {
#IBOutlet var animateButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
enableAnimateButton()
}
}
I wish there would be any easier approach, but so far you make need to do these 2 things manually: declaring your button as #IBOutlet and call a setup function. The reason why we need to import ActionKit is that we can't addTarget in protocol extension.
Right now I have a view controller that handles a lot of network requests. They are each a subclass of a NetworkRequest class and this view controller is the delegate of all of them. It implements one callback function, networkRequestDidFinish.
The problem is that all these network requests are separate objects, and they will all call that same function. What is the proper way to design this? Right now I go through a bunch of if statements in networkRequestDidFinish to see what kind of network request returned. It feels wrong though, but I am not sure what is conventional to do in this case.
Thanks.
One useful pattern here is to be sure that the delegate methods pass self to the view controller. It sounds like you might already be doing this - if you're using a series of if statements, you probably have a pointer to the relevant NetworkRequest. If you aren't, or are not sure, read on.
You see this pattern pretty much wherever delegation is used. As an arbitrary example, take the UITableViewDelegate protocol. The first argument of each of the delegate methods is a UITableView. For example:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
When a UITableView instance calls this delegate method, it passes self as that first argument. It does something like:
[self.delegate tableView:self heightForRowAtIndexPath:0];
Then, the delegate knows which UITableView it's dealing with, because it has a pointer dropped in its lap, as the argument tableView.
In your case, I would start by adding a parameter to the delegate method networkRequestDidFinish, changing its signature to:
- (void)networkRequestDidFinish:(NetworkRequest *)networkRequest
That way you can tell which instance of NetworkRequest has called the delegate method.
Already had that, or that's not good enough? Well, the next thing I'd say would be to consider whether you really need to perform different actions based on the actual class of the NetworkRequest instance that's calling the delegate method. If you're just passing along the data, the answer is probably no. For example:
- (void)networkRequestDidFinish:(NetworkRequest *)networkRequest {
[self processData:networkRequest.data];
}
That method doesn't care what class networkRequest really is. But you seem to care, since you're doing "a bunch of if statements." Then I would say that it might be a mistake to have them all hitting one delegate method. Instead, you might want to get rid of a delegate on NetworkRequest, and instead add a protocol to each of the subclasses of that class, specific to the subclass.
What?
Let's look at an example.
Imagine that one of the subclasses of NetworkRequest is FooNetworkRequest which, of course, requests foos. Its header might look like this:
// stuff...
#protocol FooNetworkRequestDelegate
- (void)fooNetworkRequestDidFinish:(FooNetworkRequest *)fooNetworkRequest;
#end
#interface FooNetworkRequest : NetworkRequest
#property (weak, nonatomic) id<FooNetworkRequestDelegate> delegate;
// stuff...
#end
You apply a similar treatment to all the other subclasses of NetworkRequest. Then, your view controller would adopt each of these protocols, and have a separate method for each subclass of NetworkRequest.
That still seems kind of dirty, right? It does to me. Maybe this is a hint that your view controller is trying to handle too many things at once. You should consider trying to spread out the responsibility for all these NetworkRequest subclasses to multiple view controller or model classes.
If that's not an option, you can at least make your view controller's source a little easier to read by using one or more categories. Put your view controller's main behavior in its .m file, as usual, and then create a category on that view controller that adopts the proper protocol(s) and handles the requests.
There are generally 2 nice procedures.
You can use block instead of the delegate. That means you can send a block to your request class either when instancing it or when you make the request.
Use a target/selector pair system to make it look kind of like adding a target to an UIButton. NSInvocation should do the trick.
I am learning to program the iphone and I wanted to do some drawing. I followed some example code and subclassed the viewcontroller and it worked fine. Now as I wanted to expand the program I came upon a design question that I could use a little help on.
I subclass myviewcontroller with mynewview. If I have any code in the myviewcontroller how do I call or reference it in mynewview and vice versa? I am not sure if I am asking this right but I am trying to understand the relationship between the class and subclass.
Objective-C objects benefit from inheritance. All classes are subclasses of NSObject, therefore you can call init on any object. If you created a custom class and gave it a method doSomethingAwesome, you are free to then implement doSomethingAwesome in any subclass of your custom class. However, declaring a method in a subclass does not add that method to the superclass. As an aside, I rarely find myself subclass sing my own custom classes. I believe that it is encouraged to maintain what is called a shallow object hierarchy. Usually I subclass the stock cocoa classes, customize to my needs and if I need custom methods in more than one subclass I will declare a category on the superclass rather than relying on inheritance to provide my custom behavior
The messaging system in Objective-C is dynamic. Every object includes a struct with information that the runtime use for introspection. Here the runtime will find a list of methods the object is able to respond. So, let's say you message an instance like this:
[mynewview someMethod];
The runtime will first check the object information to trying to find some method that will be able to respond the message. If nothing is found, then will query the super class, and so on. In fact, the runtime is much more complex, and will give any object more opportunities to respond (that's the dynamic part. For instance, mynewview might not have any method called someMethod and yet, might be able to satisfy the call, but that's something you might not want to worry right now).
From a child class you can call the superclass implementation of a given method with the keyboard super, so if mynewview is a subclass of myviewcontroller you can call myviewcontroller implementation from mynewview with:
[super someMethod];
If someMethod is both present in myviewcontroller and in mynewview, the runtime will automatically only call the child implementation, you have to call the parent implementation (if you have to) from the child implementation.