Home > other >  Swift Delegate Pattern: Delegate = Nil
Swift Delegate Pattern: Delegate = Nil

Time:11-20

I know that this question has been asked a lot here and I've read all of them, but my problem is still persistent. I wanted to include the delegate pattern in my project in order to call a function in another class from one class. But it didn't work there either. So I created a completely new project to practice the pattern again. Only for 3 hours I just can't do it. My delegate still stays in my main ViewController nil.

This is my Code of my ViewController. It is the initialised ViewController and also the master of the delegate pattern, and the buttonViewController is the servant:


import UIKit

protocol ViewControllerDelegate {
    func printTest(message: String)
}

class ViewController: UIViewController {
    
    var delegate: ViewControllerDelegate?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
    }
    

    @IBAction func perform(_ sender: UIButton) {
        let vc = storyboard?.instantiateViewController(withIdentifier: "vc") as! ButtonViewController
        if delegate == nil {
            print("Ist nil")
        }
        delegate?.printTest(message: "Test")
        present(vc, animated: true, completion: nil)
    }
    
}


I would like to call a function in my buttonViewController if the user presses the perform button and than i want to perform a segue to my buttonViewController.

This is my ButtonViewController:

import UIKit


class ButtonViewController: UIViewController, ViewControllerDelegate {


    override func viewDidLoad() {
        super.viewDidLoad()

        ViewController().delegate = self
        print("test")
        // Do any additional setup after loading the view.
    }

    /*
    // MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destination.
        // Pass the selected object to the new view controller.
    }
    */
    
    @IBAction func performsegue(_ sender: UIButton) {
        
    }
    
    func printTest(message: String) {
        print(message   "\(self)")
    }
}

I've already tried to perform a segue that first viewDidLoad is called by my ButtonViewController, then to go back to the ViewController and then call the perform method again via a button to then see whether delegate is still nil, and yes it was unfortunately still nil.

Can somebody help me with this Problem?

Best regards!

CodePudding user response:

Protocol / Delegate pattern is used to allow an instantiated controller (or other object) to communicate back to the class that created it.

So, in your scenario, you want ViewController to conform to ViewControllerDelegate ... when you instantiate ButtonViewController (which has a ViewControllerDelegate delegate var), you assign that ButtonViewController's delegate to self (ViewController).

Now, ButtonViewController can call protocol functions via its delegate.

protocol ViewControllerDelegate {
    func printTest(message: String)
}

// make ViewController conform to ViewControllerDelegate
class ViewController: UIViewController, ViewControllerDelegate {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    
    @IBAction func perform(_ sender: UIButton) {
        let vc = storyboard?.instantiateViewController(withIdentifier: "vc") as! ButtonViewController
        
        // set self as the delegate in ButtonViewController
        vc.delegate = self
        
        present(vc, animated: true, completion: nil)
    }

    func printTest(message: String) {
        print("printTest in delegate:", message)
    }
    
}

class ButtonViewController: UIViewController {
    
    var delegate: ViewControllerDelegate?

    override func viewDidLoad() {
        super.viewDidLoad()
        print("test")
    }
    
    @IBAction func buttonTap(_ sender: UIButton) {
        // call the delegate
        delegate?.printTest(message: "Sending to delegate!")
    }

}

CodePudding user response:

The delegate pattern is confusing at first for a lot of people. I find that most of my students tend to try and do it backward at first, which is I think what is going on here as well. Usually in the delegate pattern there is one view controller (A) that presents another (B). Typically some action on B should trigger some function on A (in your case, perhaps a button press on B triggers causes A to print something).

In this scenario you would have two subclasses of UIViewController: AViewController and BViewController and a protocol BViewControllerDelegate. It would be setup as follows:

  1. The protocol BViewControllerDelegate would have the function you want to be called on AViewController when the button in BViewController is pressed.
  2. AViewController would conform to this protocol and implement this function.
  3. In BViewController you would have your delegate property defined: weak var delegate: BViewControllerDelegate?.
  4. This property would be set on an instance of BViewController by an instance of AViewController to itself (the instance of AViewController) during the presentation of BViewController.
  5. The instance of BViewController would invoke the function on its delegate property in response to a button press.
class AViewController: UIViewController, BViewControllerDelegate {
    // This is 4, this segue action is invoked by a storyboard segue in the storyboard and is responsible for setting up the destination view controller and configuring it as needed (i.e., setting its delegate property)
    @IBSegueAction func makeBViewController(_ coder: NSCoder) -> BViewController {
        let bViewController = BViewController(coder: coder)!
        bViewController.delegate = self
        
        return bViewController
    }

    // Here we accomplish 2 (and also above where we declare conformance to the protocol)    
    func bViewControllerDidPerformAction(viewController: BViewController, message: String) {
        print(message)
    }
}
protocol BViewControllerDelegate: AnyObject {
    // Here we accomplish 1
    func bViewControllerDidPerformAction(viewController: BViewController, message: String)
}

class BViewController: UIViewController {
    // Here is 5
    @IBAction func buttonPressed(_ sender: Any) {
        delegate?.bViewControllerDidPerformAction(viewController: self, message: "Test Message")
    }
    
    // This is 3
    weak var delegate: BViewControllerDelegate?
}

I'm not 100% clear on what you're trying to do in your code, but I believe your ViewController should be setup like AViewController and your ButtonViewController should be setup like BViewController. Also, the line of code ViewController().delegate = self does nothing, because it creates a new instance of ViewController and sets its delegate, but this new instance is immediately deallocated because it is not actually the one being used anywhere else.

CodePudding user response:

override prepare(for segue: UIStoryboardSegue, sender: Any?)in your ViewController and modify like this

    guard let btnVC = segue.destination as? ButtonViewController else {
     return
    }
    btnVC.delegate = self
}

now you have set the reference to your delegate.try and run

  • Related