Home > Enterprise >  How to Delegate from Parent to Multiple Different Childs?
How to Delegate from Parent to Multiple Different Childs?

Time:02-20

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!

CodePudding user response:

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>
  • Related