Delegate Pattern

Design Patter:

When you want to implement decoupled and clean code, it always takes work, and if you don't have that much experience, you'll find many difficulties, however, to facilitate or guide you when implementing it, the design patter figure appears, which has the aim to seek out and customize the best practices to unravel such problems, thus making the code more flexible to receive changes later.

The Design Pattern is more linked to a concept than to the code, however, it reflects in the code. It's used by languages that use the object orientation, and today there is a great deal of the design pattern, which can be divided into:

  • Creative: inserted in the instantiation of classes.
  • Structural: allows assembling objects and classes in larger structures.
  • Behavioral: focuses on how classes and objects communicate.

There is more than 23 design pattern, in which we will talk specifically about the Delegate.

Delegate

According to the meaning within the world, it's giving somebody else a particular responsibility. This concludes that there are two subjects that this action occurs. Bringing this idea to the code, it's possible to abstract the figure of two objects that talk with the help of a protocol.

Protocol

According to Apple documentation, protocols define a blueprint of methods, properties, and other requirements that suit a particular task or piece of functionality. You can use in:

  1. Class;
  2. Structure;
  3. Enumeration;

The protocol also specifies whether each property must be gettable or gettable and settable.

Protocol and Delegate

When we create a protocol type variable, we are transforming this variable into a delegate. Delegate is the design pattern most used by the iOS developer because you can create your delegates and also find them natively in the language, for example, when we create a UITableView.

There are several benefits for making a delegate, such as leaving the code decoupled and data transfer. However, we must be careful when doing a delegate within the class type because it can generate memory leaks.

However, there are disadvantages, because creating delegates will make your class bigger, because of the protocols. Depending on the case, it's recommended to replace them with callbacks.

When working with the delegate, it's common to use the protocol to define only behaviors, for example in UITableView, there are two protocols that we frequently implement, the Delegate and therefore the DataSource, but each has its purpose. The DataSource is employed to make cells, unlike the Delegate that gives information about the cell.

Let's imagine in real life, we are in a classroom and that we need to make the call, when it's finished, we've to form a report that ought to contain the number of scholars missing and send it to the board.

Let's practice

I will adopt the view code, if you don't know what it's, visit this POST.

Let's open Xcode, do steps 1, 2, 3 to start the code view:

1- Remove name: Main

108607028-9e213180-739c-11eb-8814-6aa2e57e4f66

2- Remove the Storyboard name at info.plist

Screen Shot 2021-02-19 at 19 09 33

3- Remove the ViewController and Main.storyboard

Screen Shot 2021-02-19 at 19 10 37

4- In SceneDelegate add the code

Screen Shot 2021-02-28 at 20 31 30

Let's structure our project, it will have two ViewControllers: ClassViewController and BoardViewController, and two classes that inherit from UIView, which will be the programmatic UI of the ViewController, called UIClassViewController and UIBoardViewController.

figura 2

ClassViewController

  • Let's instantiate in the UIClassViewController in our ClassViewController.
import UIKit 

class ClassViewController: UIViewController{

    let uiLayout = UIClassViewController()

}

UIClassViewController

  • Let's make our UI programmatically in the UIClassViewController:
import Foundation
import UIKit

class UIClassViewController: UIView{


    override init(frame: CGRect) {
        super.init(frame: frame)


    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
  • Let's create our attributes:
    let lbQtStudants: UILabel = {
        let lb = UILabel()
        lb.numberOfLines = 0
        lb.font = UIFont.systemFont(ofSize: 22, weight: .medium)
        lb.translatesAutoresizingMaskIntoConstraints = false
        lb.text = "0"
        return lb
    }()

    let stepCount: UIStepper = {
        let sp = UIStepper()
        sp.autorepeat = true
        sp.maximumValue = 20
        sp.minimumValue = 0
        sp.translatesAutoresizingMaskIntoConstraints = false
        sp.addTarget(self, action: #selector(stepperCountDidChange), for: .valueChanged)

        return sp
    }()

    var btSend: UIButton = {
        let bt = UIButton()
        bt.backgroundColor = .gray
        bt.setTitle("Send", for: .normal)
        bt.addTarget(self, action: #selector(sendValue), for: .touchUpInside)

        bt.translatesAutoresizingMaskIntoConstraints = false
        return bt
    }()
  • We created a label to show the number of missing students, a stepper that will do the count, and a button to call the other view.

  • don't forget translatesAutoresizingMaskIntoConstraints = false, because we need to do the autoLayout.

  • An error will occur because we did not implement the actions of the attributes.

 override init(frame: CGRect) {
        super.init(frame: frame)

        self.backgroundColor = .systemBackground
        setup()

    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

In the UIClassViewController init, we will configure a native background, and we will also do a function that will configure:

  • Add the attributes in the view;
  • autoLayout
func setup(){

        self.addSubview(lbQtStudants)
        self.addSubview(stepCount)
        self.addSubview(btSend)

        NSLayoutConstraint.activate([

            lbQtStudants.topAnchor.constraint(equalTo: self.layoutMarginsGuide.topAnchor, constant: 60),
            lbQtStudants.centerXAnchor.constraint(equalTo: self.layoutMarginsGuide.centerXAnchor),

            stepCount.topAnchor.constraint(equalTo: self.lbQtStudants.bottomAnchor, constant: 30),
            stepCount.centerXAnchor.constraint(equalTo: self.layoutMarginsGuide.centerXAnchor),

            btSend.centerYAnchor.constraint(equalTo: self.layoutMarginsGuide.centerYAnchor),
            btSend.centerXAnchor.constraint(equalTo: self.layoutMarginsGuide.centerXAnchor),
            btSend.widthAnchor.constraint(equalToConstant: 80),
            btSend.heightAnchor.constraint(equalToConstant: 50)

        ])
}

Let's do the stepper action:

@objc func stepperCountDidChange(_ stepper: UIStepper){
    self.lbQtStudants.text = String(Int(stepper.value))
}
  • This function says: when the user clicks on the stepper, the count will be increased or decreased and will show on the label.
  • The button action will serve to call the view of the BoardViewController, but for that to happen, it would need a navigation controller, and only the ClassViewController has it. For this to happen, we will create a callBack, so you don't need to mix content from one view to another.

  • Let's create our callBack:

var sendBoard: (() -> ()) = {}

and on the button action:

@objc func sendValue(_ count: UIButton){
    sendBoard()
}

Note: if you don't know how to callback, there is this post that I wrote [POST] (swiftlearningnow.hashnode.dev/use-callback-..).

We finished our work on this view. Let's return to ClassViewController:

ClassViewController

In ClassViewController, we will implement the loadView () function because in it that our view should be called, taking advantage of the function, we will also configure our navigation controller.


import UIKit

class ClassViewController: UIViewController{

    let uiLayout = UIClassViewController()

    override func loadView() {
        self.navigationController?.navigationItem.largeTitleDisplayMode = .always
        title  = "Missing Students"
        self.view = uiLayout
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }

}

Ready! you will already be able to see the layout when buiding the app.

Let's implement the function to call the view of the BoardViewController


override func viewDidLoad() {
    super.viewDidLoad()
    callBackValueBtn()
}

func callBackValueBtn(){
    uiLayout.sendBoard = { 
        let vc = BoardViewController()
        vc.modalPresentationStyle = .fullScreen
        self.navigationController?.pushViewController(vc, animated: false)
    }
}

BoardViewController

In the BoardViewController class, it will not change much of what we have already done.

class BoardViewController: UIViewController {

  let uiLayout = UIBoardViewController()

    override func loadView() {
        self.view = uiLayout
    }

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

UI

import UIKit

class UIBoardViewController: UIView {

    var lbQtStudants: UILabel = {
        let lb = UILabel()
        lb.numberOfLines = 0
        lb.font = UIFont.systemFont(ofSize: 22, weight: .medium)
        lb.translatesAutoresizingMaskIntoConstraints = false
        lb.text = "0"
        return lb
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.backgroundColor = .systemBackground

        setup()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    func setup(){
        self.addSubview(lbQtStudants)

        NSLayoutConstraint.activate([
            lbQtStudants.centerXAnchor.constraint(equalTo: self.layoutMarginsGuide.centerXAnchor),
            lbQtStudants.centerYAnchor.constraint(equalTo: self.layoutMarginsGuide.centerYAnchor),

        ])

    }


}

Delegate

Create a delegate, it is necessary to know who the two subjects are and to follow some steps:

1- Create a protocol that has some behavior. 2- Create a protocol type variable.

Note: nessa etapa temos que ter cuidado para seguinte situação, se você criar um delegate para o tipo classe, você precisa dizer ao protocolo, par vircular somente a class/NSObject. When creating a delegate variable, it is necessary to add it to a weak variable so that there is no memory leak.

3- Create a function to delegate our behavior

4- Create the class that will conform to the protocol.

5- The class that has the delegate and the class that has the protocol must be associated often "self" is employed because both have an equivalent protocol, the variable, and therefore the class.

Let's follow the steps and implement in our project:

Who will have the delegate is the BoardViewController, and who will be associated with a protocol is the ClassViewController.

Now we will create our delegate because we want to show the amount of missing in our direction.

Step 1

The protocol will be called Countable:

protocol Countable: class {
    func qtMissing(boardViewController: BoardViewController)
}

Step 2

In the BoardViewController, you will have a delegate:

weak var deletegate: Countable?

Step 3

In viewDidLoad, I'm going to call a function to delegate our behavior.

override func viewDidLoad() {
    super.viewDidLoad()

    qtStudants()
}

func qtStudants(){
    deletegate?.qtMissing(boardViewController: self)
}

Step 4

Let's go to the ClassViewController and implement the protocol. It's a good practice to do an extension of the class to receive the protocol

extension ClassViewController: Countable{
    func qtMissing(boardViewController: BoardViewController) {
        boardViewController.uiLayout.lbQtStudants.text = uiLayout.lbQtStudants.text
    }

}

The label that is in the BoadViewController receives a label assignment that indicates the number of missing.

Step 5

We have to establish a communication between BoadViewController classes and the ClassViewController through the protocol, the only place we call the other class is in the func callBackValueBtn () function, which we use the navigation controller, taking advantage of this, we can sign the communication with the delegate so:

func callBackValueBtn(){
    uiLayout.sendBoard = {
        let vc = BoardViewController()

        //Here
        vc.deletegate = self //Here
        //Here

        vc.modalPresentationStyle = .fullScreen
        self.navigationController?.pushViewController(vc, animated: false)
    }
}

Result

603d6856c84de396227703

You can find the code here