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:
- The protocol
BViewControllerDelegate
would have the function you want to be called onAViewController
when the button inBViewController
is pressed. AViewController
would conform to this protocol and implement this function.- In
BViewController
you would have your delegate property defined:weak var delegate: BViewControllerDelegate?
. - This property would be set on an instance of
BViewController
by an instance ofAViewController
to itself (the instance ofAViewController
) during the presentation ofBViewController
. - 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