Ad

How To Delegate From Parent To Multiple Different Childs?

- 1 answer

I have this scheme:

  • HomeController
    • NavigationController
      • Step1 (child of NavigationController)
      • Step2 (child of NavigationController)

My problem is that i dont know how to pass info from Parent to all different childs, Because when i assign delegate to childs, always it take the last one. So if i call to delegate, only the last child that i have been added receive that the info

Some basic code:

class HomeController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        addSubview(showSteps)
    }
    @objc func showSteps() {
        let controller = NavigationController()
        self.present(controller, animated: true, completion: nil)
    }
}


protocol UserProtocol: AnyObject {
    func userHasChanged(_ user: User?)
}


class NavigationController: UIViewController {

    var user: User?
    weak var delegate: UserProtocol?
    
    override func viewDidLoad() {
        
        // all step childs are differents
        let step1 = Step1()
        addStep(step: step1)
        delegate = step1
        
        let step2 = Step2()
        addStep(step: step2)
        delegate = step2
        
        let step3 = Step3()
        addStep(step: step3)
        delegate = step3
        
        let step4 = Step4()
        addStep(step: step4)
        delegate = step4
        
    }
    
    func addStep(step: UIViewController){
        self.addChild(step)
        step.willMove(toParent: self)
        view.addSubview(step.view)
        step.didMove(toParent: self)
    }
    
    func userHasChanged(_ user: User?){
        // this observe new user data from firebase
        Service.shared.fetchUserData(uid: "XXX") { user in
            self.user = user
            // send new data to childs
            // ONLY SEND TO CHILD4, (THE LAST ONE OBVIOUSLY)
            self.delegate?.userHasChanged(user)
        }
    }

}

// that is a child example
class Step1: UIViewController, UserProtocol {
    
    var user: User?
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    func tripHasChanged(_ new_user: User?) {
        print("changed1")
        self.user = new_user
    }

}

class Step2: UIViewController, UserProtocol {
    
    var user: User?
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    func tripHasChanged(_ new_user: User?) {
        print("changed2")
        self.user = new_user
    }

}

class Step3: UIViewController, UserProtocol {
    
    var user: User?
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    func tripHasChanged(_ new_user: User?) {
        print("changed3")
        self.user = new_user
    }

}

class Step4: UIViewController, UserProtocol {
    
    var user: User?
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    func tripHasChanged(_ new_user: User?) {
        print("changed4")
        self.user = new_user
    }

}

*Only prints "changed4"

My question:

How can i do to send data to all different childs with one delegate (array maybe?)?. It is possible, Belongs to good practices?

Thanks!

Ad

Answer

You could create an array of delegates... but this is really not the delegate / protocol design pattern.

You could also take the NotificationCenter addObserver / post notification approach... but that is more suitable to multiple objects that are not necessarily "under the control" of the current class.

For your case - multiple child view controllers which all need to "do something" based on the same event, a better approach may be to create a "Base" view controller and make each of your "Steps" subclasses of that Base.

Here's a quick example...

// this is our "Step" base view controller
//  creates the "user" property
//  defines the "userHasChanged()" method
class StepBaseViewController: UIViewController {

    var user: String?
    func userHasChanged(_ new_user: String) {
        user = new_user
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // we can do anything that may be
        //  "common" to all "Steps"
    }

}

Any view controller that you make a subclass of StepBaseViewController will now have a user property and a default method to handle userHasChanged. In addition, anything that is "common" to the steps (such as UI elements like labels, buttons, etc) can be setup in viewDidLoad().

Now your 4 "step" class become:

class Step1: StepBaseViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // setup specific to this "Step"
    }
    override func userHasChanged(_ new_user: String) {
        super.userHasChanged(new_user)
        print("User changed to:", self.user, "in:", self)
        // do something specific to Step 1
    }
}
class Step2: StepBaseViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // setup specific to this "Step"
    }
    override func userHasChanged(_ new_user: String) {
        super.userHasChanged(new_user)
        print("User changed to:", self.user, "in:", self)
        // do something specific to Step 2
    }
}
class Step3: StepBaseViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // setup specific to this "Step"
    }
    override func userHasChanged(_ new_user: String) {
        super.userHasChanged(new_user)
        print("User changed to:", self.user, "in:", self)
        // do something specific to Step 3
    }
}
class Step4: StepBaseViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        // setup specific to this "Step"
    }
    override func userHasChanged(_ new_user: String) {
        super.userHasChanged(new_user)
        print("User changed to:", self.user, "in:", self)
        // do something specific to Step 4
    }
}

And here's a modified version of your NavigationController showing how to use this approach:

class NavigationController: UIViewController {

    // we'll simulate the user changing
    //  so on first tap the user will become "User 1"
    //  on next tap user will become "User 2"
    //  on next tap user will become "User 3"
    // and so on
    var n: Int = 0
    var user: String = ""
    
    override func viewDidLoad() {
        
        // all step childs are differents
        let step1 = Step1()
        addStep(step: step1)
        
        let step2 = Step2()
        addStep(step: step2)
        
        let step3 = Step3()
        addStep(step: step3)
        
        let step4 = Step4()
        addStep(step: step4)
        
    }
    
    func addStep(step: UIViewController){
        self.addChild(step)
        step.willMove(toParent: self)
        view.addSubview(step.view)
        step.didMove(toParent: self)
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        // simulate the "user changed" event
        n += 1
        self.user = "User \(n)"
        self.children.forEach { child in
            if let vc = child as? StepBaseViewController {
                vc.userHasChanged(self.user)
            }
        }
    }

}

With NavigationController loaded, tapping 3 times results in this debug console output:

User changed to: Optional("User 1") in: <MyProj.Step1: 0x7fbd7a21caf0>
User changed to: Optional("User 1") in: <MyProj.Step2: 0x7fbd7a21c480>
User changed to: Optional("User 1") in: <MyProj.Step3: 0x7fbd7a21d480>
User changed to: Optional("User 1") in: <MyProj.Step4: 0x7fbd7a21dca0>
User changed to: Optional("User 2") in: <MyProj.Step1: 0x7fbd7a21caf0>
User changed to: Optional("User 2") in: <MyProj.Step2: 0x7fbd7a21c480>
User changed to: Optional("User 2") in: <MyProj.Step3: 0x7fbd7a21d480>
User changed to: Optional("User 2") in: <MyProj.Step4: 0x7fbd7a21dca0>
User changed to: Optional("User 3") in: <MyProj.Step1: 0x7fbd7a21caf0>
User changed to: Optional("User 3") in: <MyProj.Step2: 0x7fbd7a21c480>
User changed to: Optional("User 3") in: <MyProj.Step3: 0x7fbd7a21d480>
User changed to: Optional("User 3") in: <MyProj.Step4: 0x7fbd7a21dca0>
Ad
source: stackoverflow.com
Ad