Home > OS >  How to assign value to an outlet after delegating it?
How to assign value to an outlet after delegating it?

Time:12-28

So my app has 3 screens: Main, Second, and Result (very simple)

  • On the Main screen, i show a label and button to change it

  • On the Second one I change label with textinput and pass it to Result (has

    navigation controller)

  • On the Last screen I show the result and 2 buttons: save and cancel

My problem is I can't assign value to Main's outlet because it's nil, and I can't do anything with viewDidLoad() because it works only once when the app starts.

What can I do to fix this? is there any function to reload view so I can assign value in viewDidLoad?

Storyboard screenshot

The whole app is here: https://drive.google.com/file/d/1mvL2fVxjOHbL4dReCwJ8poIq9G9-ezny/view

MainVC:

class MainVC: UIViewController, ResultVCDelegate {
    func passData(text: String) {
//        label.text = text   -- throws error
    }
    @IBOutlet weak var label: UILabel!
    @IBAction func change(_ sender: Any) {
        let nextVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NavVC")
        present(nextVC, animated: true, completion: nil)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

SecondVC:

class SecondVC: UIViewController {
    @IBOutlet weak var inputText: UITextField!
    @IBAction func save(_ sender: Any) {
        let nextVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ResultVC") as! ResultVC
        nextVC.labelText = inputText.text!
        navigationController?.pushViewController(nextVC, animated: true)
    }
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

ResultVC:

protocol ResultVCDelegate {
    func passData(text: String)
}

class ResultVC: UIViewController {
    var delegate: ResultVCDelegate?
    var labelText = ""

    @IBOutlet weak var label: UILabel!
    @IBAction func saveAndGoHome(_ sender: Any) {
        let mainVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "MainVC") as! MainVC
        self.delegate = mainVC
        delegate?.passData(text: labelText)
        dismiss(animated: true, completion: nil)
    }
    @IBAction func cancel(_ sender: Any) {
        dismiss(animated: true, completion: nil)
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        label.text = labelText.isEmpty ? label.text : labelText
    }
}

BTW: I did similar app with two screens and it worked like a charm... strange

CodePudding user response:

Following on from my comment.

Looking at your code, the issue is that you are not passing a reference to your MainVC to your ResultVC, and instead you are creating a new instance of MainVC, this results in a a crash because the view controller hasn't been properly created. But you don't want to create a new instance, as you need a reference to the original MainVC that you created.

You can get this by passing the reference, you will need to update all three of your ViewControllers. Something like this should work. Note I haven't tested this but this is the general principle.

class MainVC: UIViewController, ResultVCDelegate {
    func passData(text: String) {
        label.text = text
    }
    @IBOutlet weak var label: UILabel!
    @IBAction func change(_ sender: Any) {
        let nextVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NavVC")
        nextVC.delegate = self // Here we add the delegate (the reference to MainVC)
        present(nextVC, animated: true, completion: nil)
    }
}

We need to add the delegate here and then forward it on to the ResultVC.

class SecondVC: UIViewController {

    weak var delegate: ResultVCDelegate? // Add the delegate that will hold a reference to MainVC
    
    @IBOutlet weak var inputText: UITextField!
    @IBAction func save(_ sender: Any) {
        let nextVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "ResultVC") as! ResultVC
        nextVC.labelText = inputText.text!
        nextVC.delegate = delegate // Here we pass the reference to MainVC
        navigationController?.pushViewController(nextVC, animated: true)
    }
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

We can remove the code for instantiating the MainVC from the storyboard and instead just use the delegate to pass the value back to MainVC.

class ResultVC: UIViewController {
    weak var delegate: ResultVCDelegate?
    var labelText = ""

    @IBOutlet weak var label: UILabel!
    @IBAction func saveAndGoHome(_ sender: Any) {
        // Just use the delegate no need to create a new instance
        delegate?.passData(text: labelText)
        dismiss(animated: true, completion: nil)
    }
    @IBAction func cancel(_ sender: Any) {
        dismiss(animated: true, completion: nil)
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        label.text = labelText.isEmpty ? label.text : labelText
    }
}

CodePudding user response:

-> Main VC (Where you need to get data)

class MainVC: UIViewController, ResultVCDelegate {
        func passData(text: String) {
    //        label.text = text   -- throws error
        print(text)
        }
        @IBOutlet weak var label: UILabel!
        @IBAction func change(_ sender: Any) {
            let nextVC = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "NavVC")
            nextVC.delegate = self
            present(nextVC, animated: true, completion: nil)
        }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

-> Result VC (from where you need to send data)

protocol ResultVCDelegate: AnyObject {
    func passData(text: String)
}

class ResultVC: UIViewController {
    weak var delegate: ResultVCDelegate?
    var labelText = "Please Enter Something"

    @IBOutlet weak var label: UILabel!
    @IBAction func saveAndGoHome(_ sender: Any) {
        self.delegate?.passData(text: labelText)
        dismiss(animated: true, completion: nil)
    }
    @IBAction func cancel(_ sender: Any) {
        dismiss(animated: true, completion: nil)
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        label.text = labelText.isEmpty ? label.text : labelText
    }
}
  • Related