icekomo icekomo - 5 months ago 30
Swift Question

dispatch event to parent ViewController in swift

I'm coming from as AS3 background so it might be easier for me to show you what I'm trying to do with AS3. I have a UIViewController(root) and inside that I have a ContainerView. I am under the impression that the container view's UIViewController is a child of the UIViewController(root). I would like a button to be pressed on the child view controller (container view) and bubble that event up to the parent(root UIViewController). In AS3 I would have something like this

Root Class creates the child class

var childClass = new ChildClass()

childClass.addEventListener("buttonWasPressed", callThisFunction);

private function callThisFunciton(e:Event):void
{
// move the child view
TweenLite.to(childClass,1,{x:100});

}


And in the Child Class I have a button function that would deploy this event that would bubble up to the parent.

dispatchEvent(new Event("buttonWasPressed", true));


What I'm not sure how to do is get the root VC listening for that event. Because I'm using a containerView I'm not sure how to set up an outlet to that child VC and listen to things that the child is doing. I can control drag from the IB to the VC, but that just created an outlet for a UIView that represents the container view. When I println some text, I can see that the child view controller is being instantiated first before the parent VC.

I found this post that I think is pointing in the right direction.
https://craiggrummitt.wordpress.com/2014/07/14/communication-between-objects-in-objective-c-and-swift-compared-with-actionscript-part-5/

But I'm getting an error, most likely because I'm not sure how to make the connection from parent VC to child VC that's inside the container view. I have looked around and I can't seem to find much information on the topic.

Thanks for the help!

Answer

There are two ways:

1) Use a delegate protocol (recommended)

a) In your child, create a delegate protocol, and an optional property on the child VC that will hold the delegate.

protocol ChildViewControllerDelegate {

}

class ChildViewController: UIViewController {

    var delegate:ChildViewControllerDelegate?
}

b) In the delegate protocol create a method that will be called when the button was pressed, and implement a buttonWasPressed() method in the child that calls this method on the delegate. (You'll want to connect this method up with a button in the storyboard)

protocol ChildViewControllerDelegate {
    func childViewControllerDidPressButton(childViewController:ChildViewController)
}

class ChildViewController: UIViewController {

    var delegate:ChildViewControllerDelegate?

    @IBOutlet weak var button: UIButton!

    @IBAction func buttonWasPressed(sender: AnyObject) {
        self.delegate?.childViewControllerDidPressButton(self)
    }
}

c) Make your parent view controller conform to the child protocol

class ParentViewController: UIViewController, ChildViewControllerDelegate {

    func childViewControllerDidPressButton(childViewController: ChildViewController) {
        // Do fun handling of child button presses!
    }
}

c) When the child is embedded in the parent a special kind of segue is run called an embed segue. You can see it in the storyboard - it’s the line that connects the child to the parent:

shows the embed segue selected

Add an identifier to that segue in the storyboard:

shows the settings for embed segue, with the custom identifier set

And a constant for it in the parent view controller:

struct Constants {
    static let embedSegue = "embedSegue"
}

class ParentViewController: UIViewController, ChildViewControllerDelegate {

    func childViewControllerDidPressButton(childViewController: ChildViewController) {
        // Do fun handling of child button presses!
    }
}

d) Then in the parent view controller, override the prepareForSegue() method and check if segue.identifier matches what you had set as the identifier. If it does, then you can get a reference to the child view controller through segue.destinationViewController. Cast this as your child view controller, then you can set the parent to be its delegate:

struct Constants {
    static let embedSegue = "embedSegue"
}

class ParentViewController: UIViewController, ChildViewControllerDelegate {

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == Constants.embedSegue {
            let childViewController = segue.destinationViewController as ChildViewController
            childViewController.delegate = self
        }
    }

    func childViewControllerDidPressButton(childViewController: ChildViewController) {
        // Do fun handling of child button presses!
    }
}

e) Win!

2) Use NSNotification and NSNotificationCenter

You can think of these as similar to ActionScript events, however they don’t automatically bubble up through the view hierarchy like AS. Instead notifications are dispatched globally through NSNotificationCenter. I prefer to only use this when there are multiple objects that need to listen for one particular event.

You can find more information about NSNotification and NSNotificationCenter here: https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/