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 :)
Can a child view controller be the delegate of its parent view controller?
The parent should be the child's delegate.
The parent, if it needs to tell the child something, should just invoke methods.
How to Delegate from Parent to Multiple Different Childs?
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>
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.
How can I pass data from a parent view controller to an embedded view controller in Swift?
A way to achiеve this is to get the child view controller instance in the parent's viewDidLoad. It appears that the parent's viewDidLoad: gets called after the child's viewDidLoad:, which means the label is already created in the child's view.
override func viewDidLoad() {
super.viewDidLoad()
if let childVC = self.childViewControllers.first as? ChildVC {
childVC.someLabel.text = "I'm here. Aye-aye."
}
}
Passing data from embedded PageViewController to parent View Controller
The proper way would be to create a delegate protocol and have the parent conform to it (which the child then has a reference to as a weak property) or pass in a completion handler callback block to the child and have the child call that. Generally, you don't want to be directly referencing a parent from its child.
call a method of a parent view from a view embedded in a UIContainerView. swift
You can either use delegation pattern or NSNotification.
Delegation
Set the parentVC as the pageVC's delegate and remember that the parentVC must conform to page view controller's delegate protocol
class ParentClass: UIViewController, UIPageViewControllerDelegate {
// ...
pageInstanceVC.delegate = self
}
and then implement its delegate method (this is where you change the button's color), you might want to implement it in - pageViewController:willTransitionToViewControllers:
or - pageViewController:didFinishAnimating:previousViewControllers:transitionCompleted:
Complete docs can be found here
Notification
Set parentVC to listen to page change notification and implement the required method when the notification is received
// Parent VC
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "changeButtonColor", name: "kPageChangeNotif", object: nil)
}
func changeButtonColor(notification: NSNotification) {
let userInfo = notification.userInfo as Dictionary
let pageNumber = userInfo["PageNumber"]
// Change the button color
// .....
}
Then sent out notification when page is changed
// PageVC
NSNotificationCenter.defaultCenter().postNotificationName("kPageChangeNotif", object: nil, userInfo: ["PageNumber" : 2])
Remember to remove parentVC from observing NSNotificationCenter's (removeObserver
) when appropriate
Related Topics
Assemble a List of Users with Geofire/Firebase
How to Simplify Swift Enum Custom Init
Realitykit - Load Another Scene from the Same Reality Composer Project
Drawing a Gradient Color in an Arc with a Rounded Edge
How to Pass One Swiftui View as a Variable to Another View Struct
How to Call Presentviewcontroller from Inside Class
iOS 11 PDFkit Not Updating Annotation Position
Generate Avaudiopcmbuffer with Avaudiorecorder
Scenekit Physics Add Velocity in Local Space
Type '()' Cannot Conform to 'View'
Appending Text to Nstextview in Swift 3
How to Rotate an Arkit 4X4 Matrix Around Y Using Apple's Simd Library
(Appkit) Tab Insertion Inside of Nstextblock
Building an Nsoutline View with Check Marks