Make App Pie

Training for Developers and Artists

Why do we need Delegates in iOS and WatchOS?

About two years ago someone asked me a very good question: Why do we need delegates for UIViewControllers?  He thought Swift  made things easier, but this delegate stuff seems very complicated. Shouldn’t we be able to send a message or initializer between classes?

When I first learned iOS, I’ll admit it took me months to understand what happened with delegation. I found lots of confusing code and little explanation. AS I was researching some more preferences to send people to, I found the results lacking. Most often, tutorials refer to how to use an Apple factory delegate, not making your own callback. Its these callbacks which require full knowledge of delegates.

I decided it was time to update this, and to include two examples developers might run into: the iOS and watchOS versions. With the maturing of watchOS in watchOS 3 I think more developers might begin to look at developing watch apps, and there’s some twists there that might cause some confusion.

Let’s start at the beginning, so everyone understands the problem.

What is a Class?

Let’s start at the beginning, so everyone understands the problem. While we use classes in object-oriented programming, it’s good to review what they actually are. A class is a collection of data, which we call properties, and actions we can do to those properties, which we call methods.

Properties and methods are either public or private. A public method is one that classes other than the defining class can see and can use. Private means that the property or method is only usable and visible within the defining class. Other classes cannot see or use it. In Swift the private keyword makes properties and methods private. Swift’s calculated properties feature is another way to make properties private. In Swift, there is also a default state which makes a method or class public to the current target, but not other targets.

We tend not to like other people messing with our insides, and that is a good programming practice too. In general, it is best to leave what is public by other classes to a necessary minimum. Keeping properties and methods private and not exposing all of our class is known as encapsulation.

Encapsulation allows us to make code as modular as building blocks. Just as a few stubs come out of an otherwise standard sized brick, only a few usable methods come out of a class. Then they can attach to a lot of other bricks.

What is  Model –  View – Controller  or MVC?

MVC schematic blankA term heard often  when working with Xcode, is MVC. MVC stands for Model-View-Controller. It is not an exclusive term to Xcode projects. It is a pattern of programming, a good organization of any program or application in a graphics-rich environment, and arguably any environment that interacts with the user. MVC separates the major parts of an application. First it separates the data and the user interaction then adds an intermediary between them. Why is this important? You might write and publish an application for an iPhone, then decide an iPad version would be a good idea, then decide to make a watch version. With MVC, you only change one part completely, the view and possibly some of the controller. The code handling your data never changes between the versions saving a lot of time and effort.

What is a Model?

MVC schematic modelThere are parts of our program that deal with information we want to process. A pizza ordering system has a list of data giving us information about each person’s order. There may be other links to that data with more data about each customer, and about each pizza. In a pizza ordering system this is our model: the collection of all the data we will use in our ordering system. It does not in any way interact with the user. It does not display anything or does it ask for input. It is just data. Here is an example of very simple model:

class switchState{
    var state:Bool
    func textState()->String{
        if state {
            return "On"
        } 
        return "Off"
    }
    func overLoadSwitch(users:Int,load:Int){
      let totalLoad = users * load 
      if totalLoad > 100{
          switch = false
      }
}

This model is the state of a switch.  That’s the data. the model has two methods. textState(), describes the state of the switch as a string,  overLoadSwitch() turns off the switch if users multiplied by load is greater than 100 . There is a lot more methods I should add to describe the switch, but any method is changing or describing data only. There is no user input or output here. Models might make calculations but again there is no user interaction here.

What is a View?

MVC schematic viewWhere all the user interaction happens is in the view. In Xcode, most people use Interface Builder either as a scene in a storyboard or a .xib file to build their views.  A developer can programmatically create a view class to hold the different controls.

2016-08-01_07-11-48As the model never interacts with the user,  the view never interacts directly with the data. The view doesn’t do much but sit there. It might respond to a user touch with feedback such as a notifying a method somewhere, a color change when a button gets tapped or a scrolling motion at times, but that is all it does. The view does contain a lot of properties and methods and that tell us the state of the view. We can change the appearance and behavior of the view through methods and properties. The view can tell the controller that there was a change in the view, such as a button getting pressed, or a character typed. it can’t do anything about it, but it can broadcast something.

What is a Controller?

MVC schematicThe heart of MVC connects these two. Called the controller or view controller, it coordinates what happens in the model and what happens in the view. If a user presses a button on the view, the controller responds to that event. If that response means sending messages to the model, the view controller does that. If the response requires getting information from the model, the controller does that too. in Xcode, @IBOutlet and @IBAction connect Interface Builder files containing views to the view controller.

The key to MVC is communication. To be more accurate, the lack of communication. MVC takes encapsulation very seriously.  The view and the model never directly talk to each other. The controller can send messages to the view and the controller. The view and controller may do an internal action to the message sent as a method call or it may return a value to the controller. The controller never directly changes anything in either the view or the model.

So to summarize, a view, a model and a controller cannot directly change a property in each other. A view and a model may not talk to each other at all. A view can tell the controller there is a change in the model. A controller can send messages in the form of method calls to the view and the model and get the responses back through that method.

MVC schematic full annotated

How Does MVC Connect to Other MVC’s

What we’ve discussed so far is for only one scene in a much larger application. Suppose I have the following  watchOS storyboard:

2016-09-21_05-01-58

I have an a button Switch which loads a second face that has a switch.  When I decide which way I want the switch, I press done.

The Easy Direction

In Xcode we have what are known as segues. Segues are a convenience to point from one view controller to another.  When segues keep track of some things that would become cumbersome to control ourselves in a simple way. When we move from one controller to the next, the segue tells the system to open this particular view controller, which then opens up a view and model. The model and view in the new MVC setup is different then the calling one. Apple includes a method prepare(for segue:) for  iOS which give us a chance to set values in the new view controller,  and subsequently the new view controller’s view and model.

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "switch"{
         let vc = segue.destination as! SwitchViewController
         vc.switchState = false
        }

With the introduction of WatchOS came a slightly different approach.  instead of having the full model available,  watchOS sends to new controllers a single value called context. As this is type Any?,  you can put whatever you want in this value. Most often developers send dictionaries of values to other controllers through the contextForSegue method. When the destination application wakes up. the awake method  converts the context to the proper type and assigns it correctly.

override func contextForSegue(withIdentifier segueIdentifier: String) -> Any? {
        return self
    }

For both a identical  phone or watch app I could press the Switch button.  It launches  the switch face and  pass a value to the switch to be false.

The Problem Direction

We can turn the switch off and on easily enough.  But when we press Done to send it back to the original controller  is when problems show up. By the rules of MVC, we need a method to return a value. Where in a called instance can we go back to the class calling it? With an encapsulated class we can’t. There is no way to send that revised model back to the original controller without breaking encapsulation or MVC. The new view controller does not know anything about the class that called it. We look stuck. If we try to make a reference directly to the calling controller, we may cause a reference loop that will kill our memory. Simply put, we can’t send things backwards.

This is the problem that delegates and protocols solve by being a little sneaky. Imagine another class, one that is really a skeleton of a class. This class contains only methods. It declares that certain methods are in this class, but never implements them. In Swift they are protocols. We make a protocol class that has one method. That method is what you do when you are done  with the switch, and want to go back to the calling controller. It has a few parameters, things you want to pass back to the calling view controller.  So it might look like this:

protocol SwitchDelegate {
    func didFinishSwitch(switchState:Bool)
}

I passed back the state of the switch in this case.

In the controller with the switch, we make an instance of this protocol, calling it delegate.

delegate:SwitchDelegate! = nil

Since we have a property of type SwitchDelegate, we can use the methods of the SwitchDelegate type, In our example, that is our method didFinishSwitch. We can stick that method call in a action for a Done button:

@IBAction func doneButtonPressed(sender:UIButton!){
    delegate.didFinishSwitch(switchState: switchState)
    dismiss(animated: true, completion: nil)
}

or for WatchOS

@IBAction func submitSwitchStatus() {
    delegate.didFinishSwitch(switchState: switchState)
    pop()      
}

Since protocols are skeletons, it means any other class can adopt them. A class makes the protocol methods part of its own class with a stipulation. As soon as a protocol gets adopted, you need to flesh out the skeleton. The developer has to code the required methods in the adopted class’ code. We adopt a protocol by placing it after the name of the class and superclass. For iOS you might have

class OrderPizzaViewController:UIViewController,PizzaEditDelegate

and for watchOS, you might have

class InterfaceController: WKInterfaceController,SwitchDelegate {

As soon as you do that, you will get a compiler error since the protocol’s method does not exist in the class. In the code for the adopting class, in our example OrderPizzaViewController, we would implement the method

func didFinishSwitch(switchState: Bool) {
        if switchState {
            textState = "Switch is On"
        } else {
            textState = "Switch is Off"
        }        
    }

We get the data back, and what we need with it, in this case a string that we’ll print to the label.
One more step. While back in the destination controller, I said the delegate was an instance of the protocol, I didn’t say where the delegate was. In prepare(for Segue) I add one more line vc.delegate = self saying the protocol is your controller

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "switch"{
         let vc = segue.destination as! SwitchViewController
         vc.switchState = false
         vc.delegate = self
        }

In WatchOS this gets a bit trickier. I have only one context to pass both the switchState and the delegate. For multiple values generally developers use a dictionary like this:

override func contextForSegue(withIdentifier segueIdentifier: String) -> Any? {
        let context:[String:Any] = ["switchState":false,"delegate":self]
        return context
    }

the awake method would have code to unwrap the dictionary and assign the values.

When we tap Done in a watch or phone application, the method runs, it knows it is located in the calling controller, and calls it there where we added to the original class.The data is a parameter so the program can easily transfer into the controller and to the model. Delegates and protocols are bit sneaky but it works, and is one of the most important techniques when working with view controllers.

mvc-schematic

The original question asked why didn’t Swift make this simpler. As I hope I’ve shown here with a Swift context, it doesn’t matter what object oriented language you use. Delegates are part of the MVC pattern, which is a good programming practice.

8 responses to “Why do we need Delegates in iOS and WatchOS?”

  1. […] changes, I made a new post wth proper syntax, and a lot better illustrations. You can find that here on my site   or here on LinkedIn […]

  2. […] full menu we made. If you are unfamiliar with delegates and how they work, you might want to read this lesson on delegates […]

  3. […] Close the assistant editor and go to the TableViewController.swift code. The table view controller is working independently on top of the view controller. They communicate as easily as if they were separate controllers linked by segues, and I treat them like that. I’ll set up a delegate to change the label. I’m not going to explain delegation here. You can read about delegation in the article Why do we need Delegates. […]

  4. I for one, really appreciated this article … I’m all over MVCs, but this was the first article that discussed delegates in relation to fundamental MVC strict encapsulation design. I’m understanding things much better now about when to use them and why they’re needed !

    1. Thanks for your comment! I found most people, including me, don’t understand why we are doing all this until they get the encapsulation issue.

  5. Very nice tutorial and explanation! Really clear, althoug it has some incorrect code lines from the old tutorial:

    – class OrderPizzaViewController:UIViewController,PizzaEditDelegate
    – and it says “in our example OrderPizzaViewController, we would implement ..” before the “didFinishSwitch” implementation

    its still clear but it would be more if that’s corrected. thanks for this!

  6. Excellent article. Very clear. This was the 5th one I read and for the first time I got some clarity.

  7. Thanks for a helpful article Steven. I’m still grappling with this a bit though, and I think some of my confusion comes from your mixing of terms: ‘original controller’, ‘calling controller’, ‘new view controller’, ‘controller with the switch’, ‘destination controller’, and, in the diagram, just ‘Source’ and ‘Destination’. Perhaps it would be easier to follow if you stuck with the same terms throughout the article.

    The bit I found most confusing was the line ‘delegate:SwitchDelegate! = nil’, where you wrote, ‘we make an instance of this protocol’. Wouldn’t it be more accurate to say that you’re declaring a property which can then be used to store an instance that conforms to the ‘SwitchDelegate’ protocol? The instance, in this case, is the source controller, right? So you’re not really creating an instance, just providing a hook for an existing one. I think it would be clearer if it was written something like this:

    In the destination controller, we declare an optional ‘delegate’ property:

    var delegate:SwitchDelegate!

    Now the source controller can assign *itself* as the delegate when it calls the destination controller:

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if segue.identifier == “switch”{
    let vc = segue.destination as! SwitchViewController
    vc.switchState = false
    vc.delegate = self
    }

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.