Delegate Using Container View in Swift

Delegate using Container View in Swift

Looks like you defined the delegate, but have not set the delegate. This happens to me all the time.

Setting up delegates in container view

You can assign delegate in prepareforsegue. Like below code

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "container_segue") {
let controller = segue.destination as! containerController
controller.delegate = self
}
}

When project runs, this method called automatically because we had created segue in the storyboard.

By using segue.identifier you can check for which controller segue is going to happen and accordingly you can achieve your requirement.

Delegation Among Container View Controllers

Unfortunately, I don't know how the dragging and dropping works in Xcode, I do everything in code. However, when your parent view controller instantiates another view controller, just set the parent as the container's delegate.

Create the protocol:

protocol SomeProtocol: AnyObject {
func passX(a: String?)
func passY(b: String?)
}

And the containers will have delegates of type that protocol:

class FirstContainerVC: UIViewController {
weak var delegate: SomeProtocol?
}

class SecondContainerVC: UIViewController {
weak var delegate: SomeProtocol?
}

The parent must conform to the protocol so that it can become the delegate. Then when you instantiate the containers (which you must only do once in this scenario), set self as their delegates:

class ParentViewController: UIViewController, SomeProtocol {

// make the containers instance properties so that you
// can access them from the protocol methods
weak var firstContainerVC = FirstContainerVC()
weak var secondContainerVC = SecondContainerVC()

// set the delegates at some point
func setDelegates() {
firstContainerVC?.delegate = self
secondContainerVC?.delegate = self
}

func passX(a: String?) {
guard let a = a else {
return
}
secondContainerVC?.getFromFirst(a: a)
}

func passY(b: String?) {
//
}

}

Then when you want to go from first to second, go through the delegate from the first container to the parent, and from the parent to the second container.

class FirstContainerVC: UIViewController {

weak var delegate: SomeProtocol?

func sendToSecond() {
delegate?.passX(a: "slick")
}

}

class SecondContainerVC: UIViewController {

weak var delegate: SomeProtocol?

func getFromFirst(a: String) {
print(a)
}

}

This is a somewhat crude example. You should code your implementation how you feel most comfortable (i.e. gracefully unwrapping, how/where you instantiate, etc.). Also, if all of the view controllers are permanent view controllers (meaning that they are never deallocated), no need to make them weak var. However you do it, the concepts are all the same. The parent is the delegate for the containers, and the containers communicate with each other through the parent.

Some people may suggest using notification observers or singletons for the containers to communicate with each other, but I find that to be overkill when you have a parent right there.

Delegate doesn't work using a container ViewController

Since you need to call a child from a parent then no need for a delegate here , Make this an instance variable inside MainViewController

let secondViewController = SecondViewController()

then use it to call

secondViewController.sayHi()

Delegate between Container View and ViewController in Swift

Well, I don't know if this is entirely what you're looking for, but what I've always done in this situation is kept a record of each view controller inside the other's class.

For example if your container view has the embed segue with identifier "Embed Segue" then your classes might look like:

Superview Class

Class ViewControllerOne: UIViewController {
var data = "This is my data I may want to change"
var subView: ViewControllerTwo?

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "Embed Segue" {
let destinationVC = segue.destinationViewController as! ViewControllerTwo
destinationVC.superView = self
self.subView = destinationVC
}
}
}

Embedded Class

Class ViewControllerTwo: UIViewController {
var data = "This is the other view controller's copy of that data"
var superView: ViewControllerOne?
}

Then you can pass data between these View Controllers simply by referencing self.subView.data and self.superView.data respectively.

Edit: For ViewControllerTwo to pass data back to ViewControllerOne, it would then simply have to reference self.superView.data. e.g:

Class ViewControllerTwo: UIViewController {
var data = "This is the other view controller's copy of that data"
var superView: ViewControllerOne?

func passDataBack() {
self.superView.data = self.data
}
}

This would then update the data variable in the first view controller.

Delegate is nil when using container views

I have found the solution.

When using Container Views, the correct way of getting references to the embedded VCs is to implement Prepare for segue method and access the destination property inside the VC class that contains the Container Views; like so:

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "sideVCSeg" {
sideMenuVC = (segue.destination as! SideMenuViewController)
}
if segue.identifier == "centerVCSeg" {
let navController = (segue.destination as! UINavigationController)
centerVC = (navController.viewControllers.first as! centerViewController)
}
}

and because this method is executed when the view is loading I only had to add this line to viewDidLoad :

sideMenuVC?.delegate = centerVC

Delegation between two container views

The problem with your code is:

  • you were never setting your delegate to self in your BottomContainerViewController.
  • In your TopContainerViewController you set your delegates initial value to nil, just leave it as an optional and use a guard to unwrap it.

This works in my test application:

protocol TopContainerDelegate : class {
func send(text:String)
}

class TopContainerViewController: UIViewController {

@IBOutlet weak var textField: UITextField!
weak var delegate : TopContainerDelegate?

@IBAction func sendMessage(sender: UIButton) {

guard let delegate = delegate else {
return
}

delegate.send(textField.text!)
}

override func viewDidLoad() {
super.viewDidLoad()
}
}

class BottomContainerViewController: UIViewController, TopContainerDelegate {
@IBOutlet weak var messageLabel: UILabel!

override func viewDidLoad() {
super.viewDidLoad()

let app = UIApplication.sharedApplication().delegate! as! AppDelegate
if let viewControllers = app.window?.rootViewController?.childViewControllers {
viewControllers.forEach { vc in
if let cont = vc as? TopContainerViewController {
cont.delegate = self
}
}
}
}

func send(text:String) {
messageLabel.text = text
}
}

If you have any further questions feel free to download my working project and test it out for yourself.

Download Working Example

Swift; delegate embedded view controller and parent

What I ended up doing is adding this to the MainController:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

if (segue.identifier == "mySegue") {
let vc = segue.destinationViewController as! EmbeddedController
vc.delegate = self
}
}

In storyboard I selected the segue from the MainController to the EmbeddedController, and set the identifier to "mySegue".

Without the code above the delegate kept returning nil. I didn't look into this solution at first as I thought segues were only for transitioning between view controllers, and in my mind I didn't see the embedded controller as a transition. Maybe someone more knowledgable than me (which is practically anyone on here at this point) can explain how this is all fitting together.

In any case, this is how I solved my issue and hopefully someone else can benefit from this as well :)



Related Topics



Leave a reply



Submit