Photo by Clay Banks on Unsplash

For Jr.’s Who Want to Become Mid in 6 Months(!): Swift Delegation Pattern

Hello and welcome friends, in this article, I plan to explain the Delegation Pattern in the way I understand it because it’s explained the same way everywhere. There’s a vehicle protocol and since the car defined this vehicle protocol for itself, it gained the ability to drive, etc., these are entirely syntactic matters. I believe the hard part isn’t this, the hard part is us not knowing where to use it in a real application. This always happens by rote learning. In this article, I aim to explain the logic of the Delegation Pattern to you.

--

Bu yazının Türkçesi için buraya bakabilirsiniz ⬇️
For the Turkish version of this article, see here ⬇️

Grab your tea or coffee, a long article awaits you ☕️

If you get bored at some point or see something wrong, please provide feedback.

Before we start, if you’ve never heard the terms Protocol and Closure before and you’re going to panic and close when you see them, let me take you from here.👇🏼

Swift Book Official Documentation — Closure Explanation
Swift Closures Explained — Sean Allen

I mentioned that I wanted to go with real applications, so let’s design an app where we transfer data between two screens.

I’m quickly going over the view parts.

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?
// MARK: - UI Scene Caller
func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
guard let windowScene = (scene as? UIWindowScene) else {return}
let window = UIWindow(windowScene: windowScene)
let FirstVC = FirstVC()
let navController = UINavigationController(rootViewController: FirstVC)
window.rootViewController = navController
self.window = window
self.window?.makeKeyAndVisible()
}
}
/// Here, simply put, we're saying that we will start our application from a
/// Navigation Controller that has chosen FirstVC as the view controller.
final class FirstView: UIView {
// MARK: - UI Elements
private let firstLabel: UILabel = {...}()
private let firstTF: UITextField = {...}()
private let firstButton: UIButton = {...}()
/// I'm customizing the Label, TextField, and Button within the Closure.

// MARK: - Life Clycle
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}

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

// MARK: - Function
private func configure() {
backgroundColor = .systemGray5
setupFirstLabel()/// I've created these three functions with an extension
setupFirstTf()/// and aligned them to the screen with snapkit
setupFirstButton()/// I'm not filling up the screen since it's not our topic.
}

// MARK: - Action
@objc func buttonTapped(){
}
// this function will run when our button is clicked
}

If you want to create programmatic UI, I recommend these resources
Youtube codebrah
You should learn SnapKit immediately after learning programmatic UI.

final class FirstVC: UIViewController{
// MARK: - Properties
var firstView: FirstView?

// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
}

// MARK: - Functions
private func configureUI() {
firstView = FirstView()
self.view = firstView
}
}

We are setting the view of the VC to be displayed when the screen first opens to the view we designed. So why do we initially make the firstView an optional value? Since we are equating the View to be displayed on the screen to the firstView, when the life of the ViewController ends, the life of the View we display on the screen must also end so that we can display something new on the screen, that’s why we define it as a variable like this and then equate it to the View.

class SecondView: UIView{
// MARK: - UI Elements
private var secondLabel: UILabel = {...}()
private let secondButton: UIButton = {...}()

// MARK: - Life Clycle
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}

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

// MARK: - Function
func configure() {
backgroundColor = .systemGray5
setupSecondLabel()//These functions use SnapKit to
setupSecondButton()//Help place elements on the screen
}
// MARK: - Action
@objc func buttonTapped(){
}
///In this view, we define our elements in the same logic as FirstView and
///We configure these elements at Init.
class SecondVC: UIViewController{
// MARK: - Varaibles
var secondView: SecondView?

// MARK: - Life Cycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
configureUI()
}

// MARK: - Functions
func configureUI() {
navigationItem.hidesBackButton = true
secondView = SecondView()
view = secondView
}
}// Everything here is the same as with FirstVC, so I'm moving on.
Yes, we’ve defined our Views and ViewControllers, the application currently looks like this.

Now, after these processes are done, the first thing we need to do is to identify what we want, which way would make it easier to do what I want.

  1. Clicking the Button in FirstVC should go to SecondVC
  2. The text in the firstTextField should be written in the secondLabel
  3. When the secondButton is clicked, it should go to FirstVC

Now let’s fulfill our requests one by one.

First, let’s write a Protocol and name it ButtonTapProtocol and mark it as AnyObject because we only want classes to adopt this protocol.
Also, let’s define a function in our protocol that will fulfill the task we want in item 1.

protocol ButtonTapProtocol: AnyObject {
func didTapButton()
}

It doesn’t matter where all this definition is made. If you are making a very large app and using the delegate pattern, you can even open a separate Swift file just for protocols and keep all of them there. Protocols can be called from anywhere, just like standard classes and structs.

Now let’s go to FirstView because our first notified function is there when the button is clicked.
If we create an object/variable as a protocol type, that variable allows us to access the functions/content of our protocol.
How so? Like this ⬇️

weak var buttonTapDelegate: ButtonTapProtocol?

If you’re wondering about “weak”, you can refer to this

Exactly, by doing so, inside the buttonTapped() function that is triggered when the button is clicked, we will call the didTapButton function from our protocol. Why are we calling it there? Because we want the didTapButton function to be executed/called after the button is tapped. The final version of the buttonTapped() function would look like this:

@objc func buttonTapped(){
buttonTapDelegate?.didTapButton()
}

So now didTapButton will work when the button is clicked, but how will it work, we have not defined the content of this function?
Exactly, we have not defined the content of this function, and you may also remember that.
Our delegate is still nil, we defined it as optional.
Now it’s time to fill in the meaning of our delegate. To make it a representative, a proxy.

Now let’s go to FirstVC and create a function called delegateImpemantent.
Let’s create it, but why should we create it?
As I said above, the buttonTapDelegate in our firstView page is currently nil and we need to fill it, if we need to explain the another meaning of the word Delegate, we need to give it a proxy, representative, responsible person.

private func delegateImplemantent() {
firstView?.buttonTapDelegate = self
}

It was easier for me to understand some things after I had this part in my head, so we can spend some time here.

firstView.buttonTapDelegate = self, let’s go one by one.
First of all, when we say firstView, we already know that we are talking about our view class and we say that the buttonTapDelegate variable in firstView wants to be me.
Hmm how do you mean I want to be me?
Let’s call it a delegate instead of a variable, then it’s better.
Look now I want to be the buttonTapDelegate delegate in the firstView class. So buttonTapDelegate is actually our FirstVC but since we derived buttonTapDelegate from ButtonTapProtocol, FirstVC will be responsible for our ButtonTapProtocol related work.

In other words, when we clicked on the button, the didTapButton function was supposed to run, but FirstVC said that I am taking over this work/execution task. Well, okay, let it take over, but how will it take over, he doesn’t have this function, very true, before I could say these things Xcode❤️ got angry with me and says this ⬇️

So to translate, it says ⬇️

You can’t assign buttonTapDelegate here because it wants a ButtonTapProtocol delegate, agent, employee. But if you map a ButtonTapProtocol to yourself and use the necessary functions, you can be the delegate.

Well, okay, very good, that’s what we wanted and then we take the necessary actions.

final class FirstVC: UIViewController, ButtonTapProtocol {
...
private func delegateImplemantent() {
firstView?.buttonTapDelegate = self
}
func didTapButton() {

}
...
}

As we can see above, we process the Protocol in our FirstVC class and then, after obtaining the necessary functions, Xcode stops being angry with us and we are now a registered agent, employee and officer of FirstView.

You know that didTapButton that works when firstButton is clicked, since we are now buttonTapDelegate and the didTapButton function is accessed through us, we can think of that function as the didTapButton function in FirstVC.

Now we can realize our 1st point. When firstButton is clicked, we just need to do the following operations to go to SecondVC.

func didTapButton() {
navigationController?.pushViewController(secondVC, animated: true)
}
We’ve made our screen transition.

Display the text value entered in firstTextField in secondLabel.

Let’s think about what we need to do with what we remember from 1.item

  1. We should get the entered text value.
  2. We should keep the text value somewhere.
  3. Before the other screen loads, we should display this text value in secondLabel

To get the entered text value, we already have a function didTapButton that runs where the text is entered, let’s add a parameter to this function to request a String value.

func didTapButton(text: String?) {
navigationController?.pushViewController(secondVC, animated: true)
}

Let’s make it optional because maybe it will be used in other ways in the future and we can enter the text value as nil. 😉

Ok, now we can get the text value, the next step is to store this value somewhere in SecondVC and wait for the other functions that need to run to show it. If we need to wait, we can’t fetch the text after going to the second screen, we need to tell the screen what to write on the screen before our screen is shown, so let’s fetch the text, store the text and tell it what to write, then go to the screen.

To do this we need a protocol and a new delegate. Let’s think about where we need to do this.

  • As we said, it didn’t matter where the Protocol was defined, so we can create it wherever we want and name it PrepareChangeLabelNameProtocol. I say prepare it because first we have to keep the text somewhere and prepare the secondView.
protocol PrepareChangeLabelNameProtocol: AnyObject {
}
  • So what should be the functions of our Protocol, let’s think about what we want to do.
    We want to prepare the SecondView to fetch, store and display the text. Then our functions should be like this. ⬇️
protocol PrepareChangeLabelNameProtocol: AnyObject {
func getText(text: String?, handler: (()->()))
func prepareChangeLabelText()
}
  • There is a completion handler in the getText Function because I want to fetch and save the text first and then see the 2nd screen.
  • Let’s place and create our functions respectively. I will write an extension to keep things organized.
extension SecondVC: PrepareChangeLabelNameProtocol{

func prepareChangeLabelText() {
}

func getText(text: String?, handler: (()->())){
}
}
  • Let’s create a String variable at the top of SecondVC to store the value that the getText function gets when it runs, and then fill in the getText function.
class SecondVC: UIViewController{
// MARK: - Varaibles
var labelText:String = ""
...
}
extension SecondVC: PrepareChangeLabelNameProtocol{

func prepareChangeLabelText() {
}

func getText(text: String?, handler: (()->())){
if let text = text{
self.labelText = text
handler()
}
}
}

After our getText method assigns the text value it receives in its parameter to our labelText variable, it goes to execute the values written in the handler. So where do we get this parameter and handler, of course in the most convenient place for us and for our scenario this is now inside the didTapButton function in FirstVC because our didTapButton function both requests the text parameter and is the last function before going to SecondVC. But first we need to do a delegate operation to use the getText function there, since we have already done this twice, I am not explaining it in detail anymore, the last carpet of our class is like this ⬇️

final class FirstVC: UIViewController, ButtonTapProtocol {
// MARK: - Properties
let secondVC = SecondVC()
var firstView: FirstView?
var secondVCDelegate: PrepareChangeLabelNameProtocol?

// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
delegateImplemantent()
}

// MARK: - Functions
private func configureUI() {
firstView = FirstView()
self.view = firstView
}

private func delegateImplemantent() {
firstView?.buttonTapDelegate = self
secondVCDelegate = secondVC
}

func didTapButton(text: String?) {
secondVCDelegate?.getText(text: text){
navigationController?.pushViewController(secondVC, animated: true)
}
}
}

I also updated our didTapButton method and we give the text value we get from firstTextField to the getText method we access with secondVCDelegate.

Now our getText method works when we press the button and assigns the value we entered in the text field to the labelText variable.

Now it’s time to show the value in the LabelText variable in the secondLabel. We’ll use our viewWillAppear method to set this up because we want to make preparations before the screen appears and viewWillAppear runs before each time the screen appears.

We make a protocol to the SecondView that will contain the method that will change the value of the label and we will delegate this method to the viewWillAppear method of the SecondVC and the label will be prepared before the screen appears as we want.

protocol ChangeLabelTextProtocol:AnyObject {
func changeLabelText(text:String)
}
class SecondView: UIView,ChangeLabelTextProtocol {
......

func changeLabelText(text: String) {
secondLabel.text = text
}
}

Okayyy, now we need to appoint our delegation.

class SecondVC: UIViewController, ButtonTapProtocol {
// MARK: - Varaibles
var changeLAbelDelegate: ChangeLabelTextProtocol?
var secondView: SecondView?
var labelText:String = ""

// MARK: - Life Cycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
configureUI()
prepareChangeLabelText()
}

// MARK: - Functions
func configureUI() {
navigationItem.hidesBackButton = true
secondView = SecondView()
view = secondView
}
}
extension SecondVC: PrepareChangeLabelNameProtocol{

func prepareChangeLabelText() {
changeLAbelDelegate = secondView
changeLAbelDelegate?.changeLabelText(text: labelText)
}

func getText(text: String?, handler: (()->())){
if let text = text{
self.labelText = text
handler()
}
}
}

Here changeLabelDelegate will be our employee, our deputy, our governor and we tell him in the prepareChangeLabelText method that you are actually the secondView because it is easier to access the label in the secondView and we created our function there.

Immediately after making the delegation assignment, we call the changeLabelText function via delegation, that is, we run the changeLabelText function in the SecondView, then it processes the labelText value we give it to the text value of the secondLabel.

And as soon as the screen appears 🎉. the value we wrote in firstTextFielda appears on the screen.

Yes, I am very, very, very, very long and this is definitely not the best practice of this process, but I thought that the more complicated it gets and the better I can explain this complexity, the better it will sit with you, so I took this long and I followed a roundabout way.

We can move our text value.

Step 3 Click on secondButton and go back

I think I don’t need to explain this either, I think you will see and understand what we are already doing.

I put all the codes at the bottom with the unnecessary parts cut off, if it does not come to your mind because I give the codes cut off and at different times.

And if you want to download and look at it, here is my GitHub link if you want to download and look at it, I’ll take a star, your applause and your follow 😂

Follow me on Twitter, I’m constantly sharing articles that I find at least as good(!) as my own 🥳

final class FirstView: UIView {
// MARK: - Varaibles
weak var buttonTapDelegate: ButtonTapProtocol?

// MARK: - UI Elements
private let firstLabel: UILabel = {...}()
private let firstTF: UITextField = {...}()
private let firstButton: UIButton = {...}()

// MARK: - Life Clycle
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}

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

// MARK: - Function
private func configure() {
backgroundColor = .systemGray5
setupFirstLabel()
setupFirstTf()
setupFirstButton()
}

// MARK: - Action
@objc func buttonTapped(){
buttonTapDelegate?.didTapButton(text: firstTF.text)
}

}
// MARK: - UI Configure Function With SnapKit
extension FirstView {
...
}
protocol ButtonTapProtocol: AnyObject {
func didTapButton(text: String?)
}
final class FirstVC: UIViewController, ButtonTapProtocol {
// MARK: - Properties
let secondVC = SecondVC()
var firstView: FirstView?
var secondVCDelegate: PrepareChangeLabelNameProtocol?

// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
delegateImplemantent()
}

// MARK: - Functions
private func configureUI() {
firstView = FirstView()
self.view = firstView
}

private func delegateImplemantent() {
firstView?.buttonTapDelegate = self
secondVCDelegate = secondVC
}

func didTapButton(text: String?) {
secondVCDelegate?.getText(text: text) {
navigationController?.pushViewController(secondVC, animated: true)
}
}
}
protocol ChangeLabelTextProtocol:AnyObject {
func changeLabelText(text:String)
}
class SecondView: UIView,ChangeLabelTextProtocol {
let secondVC = SecondVC()
weak var buttonTapDelegate: ButtonTapProtocol?

// MARK: - UI Elements
private var secondLabel: UILabel = {...}()
private let secondButton: UIButton = {...}()

// MARK: - Life Clycle
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}

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

// MARK: - Function
func configure() {
backgroundColor = .systemGray5
setupSecondLabel()
setupSecondButton()
}
// MARK: - Action
@objc func buttonTapped(){
buttonTapDelegate?.didTapButton(text: nil)
}
func changeLabelText(text: String) {
secondLabel.text = text
}
}
// MARK: - UI Configure Function With SnapKit
extension SecondView {
...
}
protocol PrepareChangeLabelNameProtocol: AnyObject {
func getText(text: String?, handler: (()->()))
func prepareChangeLabelText()
}
class SecondVC: UIViewController, ButtonTapProtocol {
// MARK: - Varaibles
var changeLAbelDelegate: ChangeLabelTextProtocol?
var secondView: SecondView?
var labelText:String = ""

// MARK: - Life Cycle
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
configureUI()
delegateImplemantent()
prepareChangeLabelText()
}

// MARK: - Functions
func configureUI() {
navigationItem.hidesBackButton = true
secondView = SecondView()
view = secondView
}

func delegateImplemantent(){
secondView?.buttonTapDelegate = self
}

func didTapButton(text: String?) {
navigationController?.popViewController(animated: true)
}
}

extension SecondVC: PrepareChangeLabelNameProtocol{

func prepareChangeLabelText() {
changeLAbelDelegate = secondView
changeLAbelDelegate?.changeLabelText(text: labelText)
}

func getText(text: String?, handler: (()->())){
if let text = text{
self.labelText = text
handler()
}
}
}

--

--

No responses yet