You have a vc (green) and it has a panel (yellow) "holder"
Say you have ten different view controllers...Prices, Sales, Stock, Trucks, Drivers, Palettes, which you are going to put in the yellow area, one at a time. It will dynamically load each VC from storyboard
instantiateViewController(withIdentifier: "PricesID") as! Prices
We will hold the current VC one in current
. Here's code that will allow you to "swap between" them...
>>NOTE, THIS IS WRONG. DON'T USE THIS CODE<<
One has to do what Sulthan explains below.
var current: UIViewController? = nil {
willSet {
// recall that the property name ("current") means the "old" one in willSet
if (current != nil) {
current!.willMove(toParentViewController: nil)
current!.view.removeFromSuperview()
current!.removeFromParentViewController()
// "!! point X !!"
}
}
didSet {
// recall that the property name ("current") means the "new" one in didSet
if (current != nil) {
current!.willMove(toParentViewController: self)
holder.addSubview(current!.view)
current!.view.bindEdgesToSuperview()
current!.didMove(toParentViewController: self)
}
}
}
>>>>>>>>IMPORTANT!<<<<<<<<<
Also note, if you do something like this, it is ESSENTIAL to get rid of the yellow view controller when the green page is done. Otherwise current
will retain it and the green page will never be released:
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
current = nil
super.dismiss(animated: flag, completion: completion)
}
Continuing, you'd use the current
property like this:
func showPrices() {
current = s.instantiateViewController(withIdentifier: "PricesID") as! Prices
}
func showSales() {
current = s.instantiateViewController(withIdentifier: "SalesID") as! Sales
}
But consider this, notice "point X". Normally there you'd be sure to set the view controller you are getting rid of to nil.
blah this, blah that
blah.removeFromParentViewController()
blah = nil
However I (don't think) you can really set current to nil inside the "willSet" code block. And I appreciate it's just about to be set to something (in didSet). But it seems a bit strange. What's missing? Can you even do this sort of thing in a computed property?
Final usable version.....
Using Sulthan's approach, this then works perfectly after considerable testing.
So calling like this
// change yellow area to "Prices"
current = s.instantiateViewController(withIdentifier: "PricesID") as! Prices
// change yellow area to "Stock"
current = s.instantiateViewController(withIdentifier: "StickID") as! Stock
this works well...
var current: UIViewController? = nil { // ESSENTIAL to nil on dismiss
didSet {
guard current != oldValue else { return }
oldValue?.willMove(toParentViewController: nil)
if (current != nil) {
addChildViewController(current!)
holder.addSubview(current!.view)
current!.view.bindEdgesToSuperview()
}
oldValue?.view.removeFromSuperview()
oldValue?.removeFromParentViewController()
if (current != nil) {
current!.didMove(toParentViewController: self)
}
}
// courtesy http://stackoverflow.com/a/41900263/294884
}
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
// ESSENTIAL to nil on dismiss
current = nil
super.dismiss(animated: flag, completion: completion)
}
Let's divide the question into two: (1) Is there a "leak"? and (2) Is this a good idea?
First the "leak". Short answer: no. Even if you don't set
current
tonil
, the view controller it holds obviously doesn't "leak"; when the containing view controller goes out of existence, so does the view controller pointed to bycurrent
.The
current
view controller does, however, live longer than it needs to. For that reason, this seems a silly thing to do. There is no need for a strong referencecurrent
to the child view controller, because it is, after all, yourchildViewControllers[0]
(if you do the child view controller "dance" correctly). You are thus merely duplicating, with your property, what thechildViewControllers
property already does.So that brings us to the second question: is what you are doing a good idea? No. I see where you're coming from — you'd like to encapsulate the "dance" for child view controllers. But you are doing the dance incorrectly in any case; you're thus subverting the view controller hierarchy. To encapsulate the "dance", I would say you are much better off doing the dance correctly and supplying functions that perform it, along with a computed read-only property that refers to
childViewController[0]
if it exists.Here, I assume we will only ever have one child view controller at a time; I think this does much better the thing you are trying to do:
I don't think that using
didSet
is actually wrong. However, the biggest problem is that you are trying to split the code betweenwillSet
anddidSet
because that's not needed at all. You can always useoldValue
indidSet
:By the way, the order in which the functions are called is important. Therefore I don't advise to split the functionality into
remove
andadd
. Otherwise the order ofviewDidDisappear
andviewDidAppear
for both controllers can be surprising.