swiftuiviewcontrollerdelegateschildviewcontroller

Delegate change from child view to main view


I have UIViewController with mainView and ChildView. I want change label in mainView from ChildView

enter image description here

Child I add by

let to = ChildView()
self.addChild(to)
self.mainView.addSubview(to.view)
self.mainViewConstraint.constant = to.view.frame.height
to.view.frame = self.mainView.bounds
to.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]

mainView - its view, where display childView

I'm try to do this with delegate In ChildView:

protocol UserDelegate {
    func changeName(name: String)
}

class ChildView: UIViewController {

     var userDelegate: UserDelegate?

    @IBAction func changeNameBtn(_ sender: UIButton) {
        self.userDelegate?.changeName(name: "TestChanges")
    }
}

In MainView:

class MainView: UIViewController, UserDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()
        let ChildView = ChildView()
        ChildView.userDelegate = self
    }

    func changeName(name: String) {
        self.helloLabel.text = "Hello \(name)!"
    }
}

Solution

  • The problem is that you present one instance of the ChildView and set a delegate on another instance of ChildView. So the child view that is presented on the screen is NOT the same one that calls the delegate. Set the delegate when you add the ChildView:

    let to = ChildView()
    self.addChild(to)
    self.mainView.addSubview(to.view)
    self.mainViewConstraint.constant = to.view.frame.height
    
    // set the delegate here
    to.userDelegate = self.mainView
    
    to.view.frame = self.mainView.bounds
    to.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    

    Moreover, to prevent retain cycles, use weak reference to the delegate:

    protocol UserDelegate: AnyObject {
        func changeName(name: String)
    }
    
    class ChildView: UIViewController {
    
        weak var userDelegate: UserDelegate?
    
        @IBAction func changeNameBtn(_ sender: UIButton) {
            self.userDelegate?.changeName(name: "TestChanges")
        }
    }
    

    Finally, you can remove the viewDidLoad implementation from the main view:

    class MainView: UIViewController, UserDelegate {
        // this should be enough
        func changeName(name: String) {
            self.helloLabel.text = "Hello \(name)!"
        }
    }