Sending data between viewControllers using protocol+delegate in swift 4

Hello,

I'm a beginner with Swift and I'm struggling to understand the way the variables are send between page_1 and page_2 using delegate and protocols.



I have a 2-page application: ViewController1.swift and ViewController2.swift

- on page 1 (ViewController1.swift) I have a textbox (TextBoxControl) and a button.

- on page 2 (ViewController2.swift) I have a label and a button



I want to send text in thextbox from page_1 to page_2 and print it in page_2.



The problem is that the delegated is always nil (it prints on the console

... print ("Delegate is nil"). )


Technologies: XCODE Version 9.4.1, swift 4


How can I solve this?



Thanks in advance.




CODE - page1 ( ViewController1 ):

import UIKit

protocol delegate_transmisie_date_1_2{
func transmisie(text1: String)
}

class ViewController1: UIViewController {
var delegate: delegate_transmisie_date_1_2?

@IBOutlet weak var text1: UITextField!

@IBAction func btn_Send_Date_Using_DelegateAndProtocol(_ sender: Any) {
if self.delegate != nil {
self.delegate?.transmisie(text1: text1.text!)
}else{
print("Delegate is nil")
}

let selectionView = storyboard?.instantiateViewController(withIdentifier: "ID_PAGE2") as! ViewController2

present(selectionView, animated: true, completion: nil)

}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.

}


}



CODE - PAGE2 ( ViewController2 )

import UIKit

class ViewController2: UIViewController, delegate_transmisie_date_1_2 {


@IBOutlet weak var label1: UILabel!


func transmisie(text1: String, text2: String) {

label1.text = text1
}

@IBAction func btn_CloseWindow(_ sender: Any) {
dismiss(animated: true, completion: nil)
}

override func viewDidLoad() {
super.viewDidLoad()

//This is the place where the delegate must by set ?????

let pagina1 = storyboard?.instantiateViewController(withIdentifier: "ID_PAGINA1") as! ViewController1
pagina1.delegate = self

}

override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}

}

Accepted Reply

Did you try to follow step by step what is described in the link ?


h ttps://medium.com/ios-os-x-development/pass-data-with-delegation-in-swift-86f6bc5d0894


Essentially :

- create a segue between the 2 controllers

- remove all the instantiateViewController

- in prepare for segue, you set the delegate


In fact, it will work differently :

- you will get data from lhe text field of VC1 when you tap a button in VC2,

- if you just want to pass data from VC1 to VC2 when you tap button in VC1, this should be done in the prepare for segue


Here is the code


First embed ViewController1 in a navigation controller (with XCode menu Editor -> Embed in -> Navigation controller

- That will provide a return button from VC2


// FOR ViewController1

You have 2 objects :

a textField text1

a button connected to btn_Send_Date_Using_DelegateAndProtocol

plus a segue to VC2, named VCInitialToVCFinal : control-drag from the button at the very top left of ViewController1 windowController to ViewController2 and select show


import UIKit

protocol delegate_transmisie_date_1_2{
    func transmisie() -> String
}

class ViewController1: UIViewController, delegate_transmisie_date_1_2 {

    @IBOutlet weak var text1: UITextField! 
  
    @IBAction func btn_Send_Date_Using_DelegateAndProtocol(_ sender: Any) {   // should better be called performedSegue 
      
        performSegue(withIdentifier: "VCInitialToVCFinal", sender: nil)
    }


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

     // Protocol implementation
    func transmisie() -> String {
      
        return text1.text!
    }

     // Segue : we set the delegate
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let destination = segue.destination as? ViewController2 {
            destination.delegate = self
        }
    }
}


// For ViewController2

You have 2 objects

- a button, connected to getDataFromIntial

- a label label1


import UIKit

class ViewController2: UIViewController {
      
    var delegate: delegate_transmisie_date_1_2?
   
    @IBOutlet weak var label1: UILabel!          // A label where we write data we get from VC1 when we tap the button
   
    @IBAction func getDataFromInitial(_ sender: UIButton) {     // IBAction for a button in VC2, named get data from VC1
       
        let testReadFromVC1 = delegate?.transmisie()
        label1.text = testReadFromVC1
    }
   
    override func viewDidLoad() {
        super.viewDidLoad()
       
    }
   
}


How to use :


in VC1, type some text in the text field : "This will be read later from VC2"

Then click on the button in VC1

VC2 shows through the segue

Tap on the button in VC2

Then the text you typed in VC1 dispalys in the label


You have effectively transfered data fromVC1 to VC2 through the delegation.


You can also write from ViewController2 to ViewController1 (which now exist when we are in VC2). This is probably more what you are looking for.


Complement protocol:


protocol delegate_transmisie_date_1_2{
    func transmisie() -> String
   
    func writeInVC1(text: String)
}


// In ViewController1

add this

    func writeInVC1(text: String) {
       
        text1.text = text
    }



// In ViewController2

add this

a button "Transfer" connected to an IBAction

an IBOutlet for a textField

@IBOutlet weak var textFieldToTransfer: UITextField!


    @IBAction func writeDataToVC1(_ sender: UIButton) {     // For Transfer button
       
        let textToWrite = textFieldToTransfer.text!
        delegate?.writeInVC1(text: textToWrite)
    }

Replies

Please show your code where you define and use delegate.


But using delegates may not be the simplest way to transfer data between viewControllers ; there is a simple way to do this :

- you have a segue from initialVC (ViewController1) to destVC (destination)

- destVC is a subclass of UIViewController, ViewController2

- you have given it an identifier, such as TransferSegue

- in ViewController2, you have a property to receive the text to pass

var textToReceive = ""


- in ViewController1, you have a prepare for segue func where you set the parameter to pass


    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "TransferSegue" {
            if let destVC = segue.destination as? ViewController2 {
               destVC.textToReceive = TextBoxControl.text
          }
        }
     }


- in ViewController2, you have defined a UILabel IBOutlet

@IBOutlet weak var label: UILabel!


- in ViewController2, in viewDidLoad, you use textToReceive to set the label

label.text = textToReceive

Thanks Claude31, but my issue is other: i have just updated my source code to see. I think that is not properly set the delegate.

There are several errors, BUT anyway, you create a new instances of view controllers, so you loose reference to what exist already.


Errors:

In class ViewController2: UIViewController

you define pagina1, but inside viewDidLoad : it disappears as soon as you leave the function.


Could declare at the top level of the class, outside any func.

var pagina1 : ViewController1?


That would change viewDidLoad:

    override func viewDidLoad() {
         super.viewDidLoad()
         //This is the place where the delegate must by set ?????
         pagina1 = storyboard?.instantiateViewController(withIdentifier: "ID_PAGINA1") as! ViewController1
         pagina1?.delegate = self
     }


In addition,

     func transmisie(text1: String, text2: String) {
         label1.text = text1
     }


does not conform to protocol definition.


Should be


     func transmisie(text1: String) {
         label1.text = text1
     }


But, once again,

- either you define persistent references of the viewControllers and use them (then delegate is not very useful)

- or it is simpler here to use a segue.

thanks, but i stil get the same error: "Delegate is nil"


My Final Code:





// ViewController.swift

import UIKit


protocol delegate_transmisie_date_1_2{

func transmisie(text1: String, text2: String)


}


class ViewController: UIViewController {

var delegate: delegate_transmisie_date_1_2!


@IBOutlet weak var text2: UITextField!

@IBOutlet weak var text1: UITextField!


@IBAction func btn_TrimiteDate_DElegate(_ sender: Any) {


if self.delegate != nil {

self.delegate.transmisie(text1: "alex", text2: "alex2")

}else{

print("Delegate is nil")

}

let selectionView = storyboard?.instantiateViewController(withIdentifier: "ID_PAGINA2") as! ViewController_pagina2

present(selectionView, animated: true, completion: nil)


}

override func viewDidLoad() {

super.viewDidLoad()

// Do any additional setup after loading the view, typically from a nib.

}

}





// ViewController_pagina2.swift


import UIKit


class ViewController_pagina2: UIViewController, delegate_transmisie_date_1_2 {


var pagina1 : ViewController?


@IBOutlet weak var label1: UILabel!

@IBOutlet weak var label2: UILabel!


func transmisie(text1: String, text2: String) {

label1.text = text1

label2.text = text2

}


@IBAction func btn_Inchide(_ sender: Any) {

dismiss(animated: true, completion: nil)

}

override func viewDidLoad() {

super.viewDidLoad()

pagina1 = storyboard?.instantiateViewController(withIdentifier: "ID_PAGINA1") as! ViewController

pagina1?.delegate = self


}

}

The problem is that the delegate is set for pagina1, the new ViewController1 instance you create,

    override func viewDidLoad() {
        super.viewDidLoad()
   
        pagina1 = storyboard?.instantiateViewController(withIdentifier: "ID_PAGINA1") as! ViewController
        pagina1?.delegate = self
    }


not for the one from which you have called btn_Send_Date_Using_DelegateAndProtocol ; for this one, delegate is still nil !


Have a look here to see how to use delegate along with a segue,

h ttps://medium.com/ios-os-x-development/pass-data-with-delegation-in-swift-86f6bc5d0894


delegate is set in the prepare for segue, so that can work.

Ok, the problem is clear, but, I don't know how can modify my code.

Did you try to follow step by step what is described in the link ?


h ttps://medium.com/ios-os-x-development/pass-data-with-delegation-in-swift-86f6bc5d0894


Essentially :

- create a segue between the 2 controllers

- remove all the instantiateViewController

- in prepare for segue, you set the delegate


In fact, it will work differently :

- you will get data from lhe text field of VC1 when you tap a button in VC2,

- if you just want to pass data from VC1 to VC2 when you tap button in VC1, this should be done in the prepare for segue


Here is the code


First embed ViewController1 in a navigation controller (with XCode menu Editor -> Embed in -> Navigation controller

- That will provide a return button from VC2


// FOR ViewController1

You have 2 objects :

a textField text1

a button connected to btn_Send_Date_Using_DelegateAndProtocol

plus a segue to VC2, named VCInitialToVCFinal : control-drag from the button at the very top left of ViewController1 windowController to ViewController2 and select show


import UIKit

protocol delegate_transmisie_date_1_2{
    func transmisie() -> String
}

class ViewController1: UIViewController, delegate_transmisie_date_1_2 {

    @IBOutlet weak var text1: UITextField! 
  
    @IBAction func btn_Send_Date_Using_DelegateAndProtocol(_ sender: Any) {   // should better be called performedSegue 
      
        performSegue(withIdentifier: "VCInitialToVCFinal", sender: nil)
    }


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

     // Protocol implementation
    func transmisie() -> String {
      
        return text1.text!
    }

     // Segue : we set the delegate
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if let destination = segue.destination as? ViewController2 {
            destination.delegate = self
        }
    }
}


// For ViewController2

You have 2 objects

- a button, connected to getDataFromIntial

- a label label1


import UIKit

class ViewController2: UIViewController {
      
    var delegate: delegate_transmisie_date_1_2?
   
    @IBOutlet weak var label1: UILabel!          // A label where we write data we get from VC1 when we tap the button
   
    @IBAction func getDataFromInitial(_ sender: UIButton) {     // IBAction for a button in VC2, named get data from VC1
       
        let testReadFromVC1 = delegate?.transmisie()
        label1.text = testReadFromVC1
    }
   
    override func viewDidLoad() {
        super.viewDidLoad()
       
    }
   
}


How to use :


in VC1, type some text in the text field : "This will be read later from VC2"

Then click on the button in VC1

VC2 shows through the segue

Tap on the button in VC2

Then the text you typed in VC1 dispalys in the label


You have effectively transfered data fromVC1 to VC2 through the delegation.


You can also write from ViewController2 to ViewController1 (which now exist when we are in VC2). This is probably more what you are looking for.


Complement protocol:


protocol delegate_transmisie_date_1_2{
    func transmisie() -> String
   
    func writeInVC1(text: String)
}


// In ViewController1

add this

    func writeInVC1(text: String) {
       
        text1.text = text
    }



// In ViewController2

add this

a button "Transfer" connected to an IBAction

an IBOutlet for a textField

@IBOutlet weak var textFieldToTransfer: UITextField!


    @IBAction func writeDataToVC1(_ sender: UIButton) {     // For Transfer button
       
        let textToWrite = textFieldToTransfer.text!
        delegate?.writeInVC1(text: textToWrite)
    }

OK, thanks a lot.