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 yourBottomContainerViewController
. - 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
Why Is My Swift Loop Failing with Error "Can't Form Range with End < Start"
Enable + Disable Auto-Layout Constraints
Is Self Captured Within a Nested Function
Accessing a String Enum by Index
Perform a Segue Programmatically
How to Open Url in Safari and the Get Back to the App Under Uitests in Xcode 7
How in Swift to Convert Int16 to Two Uint8 Bytes
Detect Ethernet/Wifi Network Change
Get Rawvalue from Enum in a Generic Function
Performseguewithidentifier in Swift
How to Fix Error: This Class Is Not Key Value Coding-Compliant for the Key Tableview.'
Accessing Self from Instance Properties Which Are Closures
How to Install Package in Xcode via Swift Package Manager
Hide Tab Bar in View with Push
Should Iboutlet Be Weak or Strong Var