Delegation between two container views

2019-07-11 21:02发布

问题:

I'm trying to send information between two Container Views in Swift, through a delegate and I keep getting unwrapping error when performing the protocol function.

my story board layout

topContainerViewController.swift

import UIKit

protocol topContainerDelegate{
    func send(text:String)
}

class topContainerViewController: UIViewController {
    var delegate: topContainerDelegate! = nil
    @IBOutlet var textField: UITextField!
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    @IBAction func sendMessage(sender: AnyObject) {
        delegate!.send(textField.text!)  
    }
}

bottomContainerViewController.swift

import UIKit

class bottomContainerViewController: UIViewController, topContainerDelegate    {

    @IBOutlet var messageLabel: UILabel!

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

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

Question

How do I properly set up delegation between two container views?

回答1:

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



回答2:

I assume that you have both container views inside a View A, so check this out:

A more easy (and more convenient?) way would be that A would do the communication between your Container View B & C. This way, you don't have to go trough the whole app to set the delegate, which also should have a better performance in case your application is really big.

View A:

@objc
protocol InformContainerDelegate{
    optional func notifyB()
    optional func notifyC()
}

var bDelegate: InformContainerDelegate
var cDelegate: InformContainerDelegate

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "toContainerB"{
        let controller = segue.destinationViewController as! ContainerBViewController
        controller.delegate = self // A receives notifications from B
        self.bDelegate = controller // sending information from A to B


    } else if segue.identifier == "toContainerC"{
        let controller = segue.destinationViewController as! ContainerCViewController
        controller.delegate = self // A receives notifications from C
        self.cDelegate = controller // sending information from A to C
    }
}

A is now the delegate of both B & C and inverse.

Let's assume that you want B to inform C about somehting:

  1. B will notify A with a protocol because A conforms to it
  2. A will send with his own delegate send the information to C

View B:

protocol BChangedSomethingDelegate{
     func informC()
}

class B : UIViewController // or whatever {
     //... usual methods
     func BDidSomething(){ delegate?.informC!() }
}

Now you need to make A conform to the protocol of B "BChangedSomethingDelegate":

extension A: BChangedSomethingDelegate {
    func informC!(){
        cDelegate!.notifyC!()
    }
}

For C you would also have to implement a protocol which A will then conform to it. Worked on that solution and it's working pretty good.

extra tip: set your breakpoints to test if A,B and C are the correspondive delegates, during the breakpoints press "po xxx" directly in the logs while the app is stopped by breakpoint to check what xxx is.

Example: (po cDelegate) -> Nil or the delegate



回答3:

DUDE You missed a step:

  1. You created protocol.
  2. Created protocols's object in topContainerViewController
  3. Implemented protocol in bottomContainerViewController

Thats cool..

But where have you specified the delegate in topContainerViewController 's instance would be instance of bottomContainerViewController ???

YOU MISSED ONE STEP: you haven't assigned object to delegate object of topContainerViewController 's instance SO YOU GOT ERROR It means you have not specified who responds to any call to delegate function's call from that object???? if you have not given who responds to it then its obvious you get error. since, i didn't find you have created instance of topContainerViewController, i guess you did by using segue.

when you create instances of topContainerViewController you need to specify delegate variable. If you go to topContainerViewController on button click from bottomContainerViewController then get the instance of bottomContainerViewController and assign self to delegate:

SOLUTION

if you do by segue: give identifier to segue here i gave "openTopContainerViewController" now Override prepareforsegue function in bottomContainerViewController

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { 
    if segue.identifier == "openTopContainerViewController" {
        let instanceOfTopContainerViewController = segue.destinationViewController as! topContainerViewController

//######## HERE IS THE PROBLEM . YOU DIDN"T ASSIGN ANYTHING TO delegate variable so It became always nil to avoid it you need to assign like below
        instanceOfTopContainerViewController.delegate = self
    }
}

If you go to topContainerViewController by code: do like this: (keep like this code in action of button pressed or whenever you trigger to open the topContainerViewController)

//First you need to give storyboard id to view controller associated with topContainerViewController then
let storyBoard = UIStoryboard(name: "Main", bundle: nil)
let instanceOfTopViewController = storyBoard.instantiateViewControllerWithIdentifier("topContainerViewControllerIdentified") as! topContainerViewController
//######## HERE IS THE PROBLEM. YOU DIDN"T ASSIGN ANYTHING TO delegate variable so It became always nil to avoid it you need to assign like below
instanceOfTopViewController.delegate = self

self.presentViewController(instanceOfTopViewController, animated: true, completion: nil)

HOPE YOU GOT IT :)

HERE IS THE PROBLEM . YOU DIDN'T ASSIGN ANYTHING TO delegate variable so It became always nil to avoid it you need to assign like above



回答4:

check whehter value of delegate nil!

change

@IBAction func sendMessage(sender: AnyObject) {
    delegate!.send(textField.text!)
}   
                                                                                                           to                                                                                                     

@IBAction func sendMessage(sender: AnyObject) {                                                    
    if let delegate = delegate {
       delegate.send(textField.text!) 
      }
}