Detached Controller Issues on a Dynamically Loaded UIStackView - ios

I am getting a "Presenting view controllers on detached view controllers is discouraged" warning in a somewhat specialized architecture. And - there are some fairly big UI issues resulting from it. I have an architecture with 2 distinct unconnected groups in my storyboard. The first group is the main interface of my app and includes an UIStackView. The second group consists of an UIView plus attached popover segue as shown in the image below.
I dynamically populate the UIStackView of group 1 with up to 8 instances of the UIView of group 2. This is done in a function called loadViews() in the UIStackView subclass which is called as needed. Here is the cleaned up pseudo code for illustration:
for i in 0 ..< green.count {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let greenVC = storyboard.instantiateViewControllerWithIdentifier("greenViewController") as! GreenVC
greenVC.progressionStackView = self
greenVC.index = i;
greenViewControllers.append(greenVC)
if let greenView = greenVC.view as! GreenView! {
greenView.fillColor = UIColor.orangeColor()
greenView.setNeedsDisplay()
self.addArrangedSubview(greenView)
}
}
self.layoutIfNeeded()
Every time I trigger the popover on one of the embedded green views in the stack view I get the warning from above. More importantly, when running on an iPad in split view mode, the stack view loses a green view each time until there are none left. The latter is just a display issue, because on refresh all views are back.
I am completely stumped and am not sure how to fix this or implement things differently. If the issue is that the loaded views are not attached, can they be "re-attached"? Or is there a way to dynamically load a stack view with up to 256 views that are attached to it?

Solved:
Another lesson in taking Xcode warnings to heart - even if the word discouraged is used. As in this case, things tend to break. The solution was obvious in hindsight. The new view controllers that were instantiated as greenVC had to be attached to the containing view controller - i.e. the one several layers up in the view hierarchy that contains this UIStackView:
vcContainingStackView.addChildViewController(greenVC)
The line above is called right before appending greenVC to my array of added view controllers. Of course now removeFromParentViewController() has to be called as well where instances of GreenVC get removed, but otherwise that's it. The warning is gone and so is the issue of vanishing views.

Related

Open same View Controller

Using Xcode 9 / Swift 4, I have a tile application (same as Windows 10 presentation).
When user is on main view controller and click on a tile, I want to open the same view controller and set parentId, in order to show child tiles.
App can have infinite number of child of child.
But, app crash when I try to push same View Controller as current displayed:
let vc = currentStoryboard.instantiateViewController(withIdentifier: "TileViewController") as! TileViewController
vc.parentId = id
vc.title = item.name
currentViewController.navigationController?.pushViewController(vc, animated:true)
The exception your are having its not related of pushing the same instance another time. Well you are probably adding some constraints programmatically, And also its being added in viewDidLoad/viewWillAppear/viewDidAppear because thats what can i understand from the exception that your getting:
Impossible to set up layout with view hierarchy unprepared for constraint.
If its so then you'll need to add it in either updateViewConstraints() which is called when the view controller's view needs to update its constraints, or inside viewWillLayoutSubviews().

iOS Swift 4 - how to pass data to splitviewcontrollers master

If anyone is well experienced with split view controllers and can help me with, it would be a great help.
I have a project that begins with a login screen, followed by a search screen that calls an API to get the results. After the search screen gets the results from the API call, I want to pass those to a splitviewcontroller for display. Basically to the splitviewcontroller’s master, which has the table view. So how to do this? I don’t see how can I use performsegue, when the segue’s from splitviewcontroller to the master don’t have any identifiers?
So is it possible to use splitviewcontroller as your third or fourth screen?
Is there any alternate way to achieve this?
Thanks.
Every UIViewController has a property called splitViewController. This is an optional value that may or may not be nil based on if you are using a UISplitViewController. The splitViewController property will allow you to access the view controllers that the split view controller has embedded.
With UISplitViewControllers there are two main components, the master and detail view controllers. On iPad the master is the one that is on the left side of the screen and takes up the least amount of space and the detail will take up the large area to the right. On iPhone both will be on top of each other in a navigation stack and both will take up the entire screen.
This is how to access the detail from the master:
if let splitVC = self.splitViewController, let detailVC = splitVC.viewControllers[1] {
detailVC.doSomething()
}
And this is how to access the master from the detail:
if let splitVC = self.splitViewController, let masterVC = splitVC.viewControllers[0] {
masterVC.doSomething()
}
Keep in mind you may be using a UINavigationController or UITabBarController within your storyboard that the master or detail may be embedded within. In that case you do the same but cast those view controllers as either UINavigationController or UITabBarController and drill down within them to retrieve your true master and detail.

Swapping VC's in a Container View - childVC does not fit Container

Latest Swift/XCode/iOS.
OK, I am trying to get my head around swapping out Child VC's in a Container View. I have read several posts describing how to do it, and they have been very helpful. However, I seem to be having a constraints issue that I can figure out.
I have a Master VC that contains some Container Views. The one Container contains several buttons (acting like a tab bar). When you tap on a button, the view controller in another "Detail" Container changes Child VC's. Code below:
#IBAction func ToBioPage(_ sender: UIButton) {
//SET RIGHT PANE VIEW TO BIO PAGE
//INSTANTIATE NEW VC
let vc = storyboard?.instantiateViewController(withIdentifier: "Biography")
//REMOVE OLD VC
detailPaneVCReference?.willMove(toParentViewController: nil)
detailPaneVCReference?.view.removeFromSuperview()
detailPaneVCReference?.removeFromParentViewController()
//ADD NEW VC
ParentVC?.addChildViewController(vc!)
vc?.view.frame = (detailContainerView?.bounds)!
detailContainerView?.addSubview((vc?.view)!)
//ADD CONSTRAINTS
//TOP ANCHOR
vc?.view.topAnchor.constraint(equalTo: (vc?.view.superview?.topAnchor)!).isActive = true
//BOTTOM ANCHOR
vc?.view.bottomAnchor.constraint(equalTo: (vc?.view.superview?.bottomAnchor)!).isActive = true
//LEADING ANCHOR
vc?.view.leadingAnchor.constraint(equalTo: (vc?.view.superview?.leadingAnchor)!).isActive = true
//TRAILING ANCHOR
vc?.view.trailingAnchor.constraint(equalTo: (vc?.view.superview?.trailingAnchor)!).isActive = true
vc?.didMove(toParentViewController: ParentVC)
}
The VC's of the Detail Container swap out but I cannot get the new one to fit completely. The Child VC runs past the bottom and right sides of the screen. The presence of the constraints added above do not even seem to make a difference. I get the same result whether they are there or not. I was expecting the frame = bounds line of code to ensure that the child fit completely within the Detail Container.
What am I missing/doing wrong?
Edit: Sorry, I forgot to mention that "detailPaneVCReference" is a reference to the starting VC within the Container View, grabbed during the embed segue. "detailContainerView" is a reference to the Container View itself, also grabbed during the embed segue. "ParentVC" is a reference to the MasterVC grabbed in the same fashion.
OK crisis over. Turns out I was designing in storyboard for a 12 inch iPad and running as a 9.7 inch iPad. Several views had fixed value heights/widths - hence the wonky appearance when running the app.

Assigning two views to a single Container View using swift in iOS 8

I am trying to assign two view controllers to a Container View using the Interface Builder. I tried to do so, but whenever I try to "embed" the second view controller to my Container View, instead of adding another VC to it, it just replaces the one that was embedded already.
Ultimately, my main goal is to have a screen that has the following elements (in order, from top to bottom):
-A navigation bar
-A view of height 50 that contains a segmented controller (which will switch between tableVCs)
-A main view, which will contain my Container view
-A tabbed bar
My current setup is almost as described above. Here is a picture:
The view controller I am interested in the most is in the one with the highlighted container(HomeViewController). The approach I am currently using is hacky, because I currently have 2 container views, one on top of another, and they embed the 2 table view controllers depicted to the right (one per container).
I do not like this approach very much because both containers get instantiated whenever the main VC (Home View Controller) is instantiated, therefore making 2 network calls by default to load their content, possibly slowing down the device and maybe using more memory than needed.
Ideally, I would load the content of one table view controller that is mapped to one of the segmented controls. Then, I would have a mechanism that somehow instantiates the second table view controller whenever I go to the second button in the segmented control (and possibly deallocating/getting rid of the other VCs), and so on with the third. Or somehow be able to display/alternate between 2 or more view controllers in an area (view) inside my HomeViewController.
Currently I have this simple code that switches (hides and shows) between container views in my HomeViewController:
#IBAction func segmentChanged(sender: AnyObject) {
switch segmentedControl.selectedSegmentIndex{
case 0:
println("index1 selected")
containerView1.hidden = false
containerView2.hidden = true
break
case 1:
println("index2 selected")
containerView1.hidden = true
containerView2.hidden = false
break
default:
containerView1.hidden = false
containerView2.hidden = true
break
}
}
As I said, this only switches between the views that are loaded already in my view controller, with the data in them already.
I just wanted to see if what I am trying to code is doable, or if I am actually tackling the problem the right way, although I doubt I am doing so.
Thank you for reading my post and for your advice in advance.
Cheers!
Add embed segue to NavigationController, add ViewController as rootViewController to NavigationController, from rootViewController add as many segue as you wish. To load controller you need just override segue class to push without navigation.
class NoAnimationSegue: UIStoryboardSegue {
override func perform() {
self.sourceViewController.navigationController?.pushViewController(self.destinationViewController, animated: false)
}
}

Removing objects from memory when switching storyboard scenes

I am creating all of my display objects programatically and adding them to my storyboard scenes subview.
For example:
let button: UIButton = UIButton( frame: CGRect( x: 0, y: 0, width: 160, height: 50 ) )
self.view.addSubview( button )
When changing storyboard scenes, I am experiencing huge memory leaks (memory usage almost doubling between every scene change), which of course shows that these objects are not being removed automatically when changing scenes. I have read some things about garbage collection in Swift, but didn't find much info on this particular use case.
By changing storyboard scenes, I mean via present view controller, like so:
self.presentViewController( targetController, animated: true, completion: nil )
So with that said, my question is:
1) Removing objects can be done like so:
button.removeFromSubview()
button = nil
correct?
2) Is there a way to remove any object inserted into the view's subview in a loop and also assign it to nil to completely remove any references associated with the object? I can remove them one by one, but it's a pain staking task.
3) Is there anyway to get objects removed automatically from memory after changing scenes? This would be the best solution.
An example of a solution would be most appreciated.
Thanks in advance.
Some things that might be clarifying:
1) Buttons that are added via storyboard (those tagged #IBOutlet) are weak; you do not need to nil them out, because your reference to them doesn't keep them in memory.
2) presentViewController probably doesn't behave in the way you expect. Most importantly, doesn't replace the existing view controller with the new view controller; it "presents" the new view controller from the old view controller. To illustrate this, you can call self.presentingViewController from the new view controller, which will provide you with a reference to the previous view controller; it's still in memory.
However, view controllers aren't super big, and they have their views unloaded when they aren't on screen. If however you are holding on to some large resources in the presenting view controller, these will persist. I would suggest addressing this by loading those assets in viewWillAppear/viewDidAppear (instead of viewDidLoad) and then unloading them in either prepareForSegue: or viewDidDisappear (I think there's some long-standing issue with didDisappear not getting called consistently? I have some sort of mental flag around there but I'm not sure what the source is...)
Alternatively, if what you actually want to do is to fully change to a new root view controller, you can do this through your AppDelegate's .window property, like this:
let storyboard = UIStoryboard(named: "NewStoryboard", bundle: nil)
let newVC = storyboard.instantiateInitialViewController
UIApplication.sharedApplication().delegate.window.rootViewController = newVC
I wouldn't overuse this, though; it's mostly useful in places where you maybe show a tutorial on first launch, and then afterwards want to load the normal view hierarchy.
If by changing scenes you mean that you are adding a new view to the stack, then it is my understanding that the previous view remains there just waiting to be popped to the top of the stack again and therefore your objects should not be removed. Someone will correct me if I have this wrong. Regardless, the code to remove all of the objects from a view is:
for sv in view.subviews {
sv.removeFromSuperview()
}

Resources