How to correctly dismiss previous view controller - ios

Apologies as I know there are some similar questions, but I've been looking for two weeks through every one I can find, and cannot figure it out (I'm a bit of a novice).
I have a few different View Controllers, not using a Navigation Controller. I can segue between them no problem. The issue is, I need each view to be dismissed when I segue to a new one. Here is some of what I've tried so far.
Option 1 (in new View Controller)
override func viewDidAppear(_ animated: Bool) {
presentingViewController?.dismiss(animated: false, completion: nil)
}
Option 2 (in old View Controller)
override func viewDidDisappear(_ animated: Bool) {
self.dismiss(animated: false, completion: nil)
}
In both of these cases, the new view gets dismissed and I'm taken back to the old view. I've tried about 20 versions of similar code.
Should I be using the first VC in my program as my "main" view controller, and presenting/dismissing all others on top of it? I didn't think this approach seemed memory efficient, when the "main" VC is not often used after initially loading the app.
It seems like I'm missing or not understanding something. Any help would be greatly appreciated.

Think of it this way: A view controller can't exist on an island. It has to be presented on top of something.
That means when you present one VC on top of another, the presenting view controller is the "foundation" for the new one you just presented.
If you don't want to present VCs on top of each other, you have a couple of options:
1) Use a navigation controller. This is probably the best approach. You can present or push any view controller. If you decide to push, you can remove the old one from the navigation stack, or you can keep it there so the user can go back. There are lots of ways to use a navigation controller, and it's easily the most flexible way to navigate between controllers.
2) Use a tab bar controller. This works best if you have just a few different view controllers in your app, but it's good for certain use cases.
3) Do exactly what you said in your post (use the root view controller to present/dismiss all other VCs). As I said, you can't present a view controller out of thin air-- there always has to be something behind it. Unless there's a ton of stuff going on in your root VC, this shouldn't cause any memory issues. This approach should be fine unless you're very particular about the animations between your view controllers.
In general, I wouldn't worry too much about memory usage until it becomes a problem. It should be fine to present view controllers on top of each other for 99% of normal use cases.

if you want to present VC B from VC A and want to dismiss VC A while Presenting you can use this Code
let parentVC = presentingViewController
dismiss(animated: true) {
let vc = self.storyboard!.instantiateViewController(withIdentifier...)
parentVC.present(vc, animated: true)`enter code here`
} `enter code here`

Related

Complex navigation stack in Swift

I have a complex navigation problem that I was hoping someone may be able to give me some insight as to the best way to approach this problem. I've been Googling and have found multiple different suggestions for similar problems, but none of them seem to quite nail down my problem.
I have a UITabBarController. Each tab is set up with it's own Navigation stack, as it should have.
One of those tabs uses a UISegmentedControl with three segments. Each of those segments needs to have its own view controller and navigation stack, independent of one another and of the navigation stack that the tab bar controller controls for its tab. Basically, it needs to work like a UITabBarController within a tab of a UITabBarController, but display the tabs as a UISegmentedControl.
Also, when at the root controller of each tab, the navigation stack should be the stack that it came in from, so that it pops out to the table view that it came in from.
I hit a snag and feel like I would have to have some brutal hack to go forward, no matter what I try. Any and all help will be greatly appreciated.
I would strongly prefer to be able to handle the connections in Storyboard, so as to keep my storyboard nice and pretty, but if it must be done in code, I can do that too.
Edit
I have something of a solution. In the base class:
private func show(segment: SelectedSegment) {
var equipmentStoryboard: UIStoryboard!
switch segment {
case .SelEquipment:
equipmentStoryboard = UIStoryboard(name: "EquipmentDetailEquipmentSegment", bundle: nil)
case .SelHistory:
equipmentStoryboard = UIStoryboard(name: "EquipmentDetailHistorySegment", bundle: nil)
case .SelPlans:
equipmentStoryboard = UIStoryboard(name: "EquipmentDetailPlansSegment", bundle: nil)
}
if let newSegmentController = equipmentStoryboard.instantiateViewController(withIdentifier: segment.getSegmentIdentifier()) as? EquipmentDetailSegmentsViewController {
var controllerStack = self.navigationController?.viewControllers
controllerStack?.removeLast()
controllerStack?.append(newSegmentController)
self.navigationController?.setViewControllers(controllerStack!, animated: false)
}
}
#IBAction func didTapSegmentedControl(_ sender: UISegmentedControl, forEvent event: UIEvent) {
let newSelectedSegment = SelectedSegment(rawValue: sender.selectedSegmentIndex)
self.show(segment: newSelectedSegment!)
}
I'm not happy about it though. I'd like to:
Not have to re-instantiate each controller every time the segment is tapped.
Be able to storyboard it (may not be possible without an actual UISegmentedControlController or something like that)
I actually found the right solution to the problem. I had to write a custom system. I'm going to come back to this and work on it at some point so that I can provide a github project with the API that would provide this functionality to anyone publicly.
Basically, the system does this:
Scrollable Segmented Control that is expandable to as many segments as desired.
A "SegmentNavigationController", which is a UIViewController because it contains views that are swapped to, brought to front, shown, (and rest hidden) when tapping on the respective segment.
Each segment has it's own navigation stack, which is simply an array of the view controllers that control the views in the current segment.
Custom animation takes place when pushing and popping an item to/from the navigation stack, to make it appear to be a natural iOS controller. The segmented control stays in place, while the new view animates in.
Navigation is retained, so that when switching between segments, you can resume where left off when tapping a different segment.
Utilizes the Clean Swift Architecture: http://clean-swift.com
I'm posting this as an answer because a custom system was the best solution after days of deliberation on the subject.
This is of course also a preliminary to possibly expanding this system to something useful and reusable.

Can't switch SCNView/UIViewControllers properly to prevent memory buildup

I have a very linear app that uses a Navigation controller to go through a series of View Controllers. All segues are handled in the storyboard and set to "Push" (that's the only way I can get the navigation controller to work...)
Start with a 'menu' VC with options, select one and it takes you to another VC. Sometimes the next VC is another menu (more detailed) but sometimes the VC is an SCNView that shows an SCNNode. Navigating through the VCs is working perfectly, thanks to the Navigation Controller.
The problem comes whenever you go to one of the SCNViews and then back out (to a menu or previous VC). Particularly, switching from menu to SCNView, back to menu, back to same SCNView, over and over builds up memory until the app crashes.
ARC seems to not be releasing the SCNNodes/SCNViews when navigating out of them. Every SCNNode/Scene/View I set up as weak AND I've tried all kinds of combinations of the following code in each/all the VCs:
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(true)
self.dismiss(animated: true, completion: nil)
}
I put this at the end of each VC but it still seems that the SCNNodes are getting retained and the memory builds up until it crashes.
How can I clear the SCNNodes when I exit the VC? Since I declare everything as weak inside ViewDidLoad, I can't call/set anything in the ViewWillDisappear func since it's outside of that.

Correct way of removing (deleting) a view/viewController from the stack after segue in Swift

I am running into a problem here. I am presenting views with performSegueWithIdentifier. Everything goes smoothly. The problem is that as a test, I only have 2 viewControllers with some data in them and I have two buttons that call a segue back to the other VC. If I keep performingSegues, you can clearly see that the memory usage goes up every two segues by around 0.4Mb. This tells me that the Views are not being deleted/removed from the view stack and are just using memory. I would like to know the correct way of getting rid of the view that presents the other view by using performSegueWithIdentifier (of course, after it finished presenting the view, else it will not work I guess).
Could you point me in the right direction? I found some objective-c code that tried to do this but it was very extensive and I don't know much about objective-C, so it was a little hard for me to understand.
Thank you in advance for your help!
Cheers!
Edit:
I am doing a "manual segue". By this I mean I have two view controllers standing on their own. They are not embedded in any navigationVCs or something like that. I am using an "Adaptive Segue" of type "Show".
If your goal is to always keep one VC, and since you already have a manual segue, what you can do is, after presenting the next VC you can pop the existing VC from the root and assign the destination VC as your root VC like so
class ManualSegue: UIStoryboardSegue {
override func perform() {
sourceViewController.presentViewController(destinationViewController, animated: true) {
self.sourceViewController.navigationController?.popToRootViewControllerAnimated(false)
UIApplication.sharedApplication().delegate?.window??.rootViewController = self.destinationViewController
}
}
}
When you do normal segues, you are piling up new view controllers on top of the existing ones without dismissing them. That's why your memory usage keeps going up.
You need to provide more information before we can give you specific guidance. Are you doing modal segues or pushing onto a navigation stack?
If you're pushing, you want to pop to the view controller you came from. Think in terms of a stack of plates. When you push, you add plates to the stack. When you pop, you take plates off and reveal the plates (view controllers) underneath. Do a search in the Xcode help system on "PopTo" to find the methods that let you either pop back to a specific view controller or back to the root view controller of your navigation controller.
Modal view controllers stack on top of each other in a similar fashion, but without the origination of a navigation controller. If you've pushed a series of one or more modals and you send the dismissViewControllerAnimated:completion: method to the view controller you want get back to it will dismiss the modals that are on top of it.
You can also look at using unwind segues. Unwind segues let you go back where you came. Do a search in the Xcode help system on "Using unwind segues" for more information. Most of the tutorials on unwind segues are written in Objective-C but you should be able to find some in Swift if you look. Explaining unwind segues in enough detail to be useful is more than I can cover in this answer.

Alarming memory increase with custom segue

I'm creating an app with XCode 6 (Swift) and recently managed to make a custom segue that doesn't use an animation. What I soon realised was that, when switching between two view controllers (tied together with the custom segues), the application memory usage (viewable with XCode) seemed drastically increase with every view controller switch. Is this something that I need to worry about? Does Swift automatically take care this memory increase, so that the app doesn't end up using an insane amount, or am I doing something wrong?
NoAnimationSegue.swift
import Foundation
import UIKit
#objc(NoAnimationSegue)
class NoAnimationSegue: UIStoryboardSegue {
override func perform () {
let src: UIViewController = self.sourceViewController as UIViewController
let dst: UIViewController = self.destinationViewController as UIViewController
src.presentViewController(dst, animated: false, completion: nil)
}
}
When you go from the first view controller to the second, you can presentViewController, but when you go back to the first, you do not want to "present" again. When you "present" one view controller from another, it keeps the first one in memory, so it's there, ready for you when you "dismiss" the second view controller to return back to the first.
Thus, if you repeat that process of presenting from one view controller to another in a circular fashion, you'll end up with multiple instances of the view controllers in memory. If you "present" to go from one view controller to another, then you'll want to unwind or dismiss to get back to the first view controller.
Alternatively, if you don't want to use the present/dismiss pattern, you can alternatively use tab bar controller if you want to jump back and forth between view controllers. Or you can use page view controller. Or you can use your own custom container view controller and replace the child view controller as you jump back and forth.
But just remember, if you present or push from one view controller to another, then you'll have to dismiss or pop to return back.

handle view controller of storyboard in AppDelegate class

I have a problem again. I work with storyboard in Xcode 6 with Swift as programming language. Before I want to present a view (with a view controller) on starting my app I want to test the internet connection and my server connection. If both connections are available I want to present a view1 and if not I want to present a view2. But I don't want to show a view with a spinner while checking the connection. So I thought I can manage it over the AppDelegate class. In the function func application() I want to decide which view (view1 or view2) is loaded at first. But for this solution I have to instantiate two view controllers that are connected to my two views in storyboard. I don't know if it is possible.
So my question, is it possible to instantiate these two specific storyboard view controllers in my AppDelegate class? And if it is possible, how can I do this by code?
If it is not possible, how can I solve my problem? At the moment I always show a view controller with a spinner (view0) and if connection is available I go to view1 and if connection is not available I go to view2 from my view0 controller like this:
override func viewDidAppear(animated: Bool) {
//some code
self.presentViewController(view1, animated: true, completion: nil)
//some other code
}
You can load a specific view controller from a storyboard with this call:
let viewController = self.window?.rootViewController?.storyboard?.instantiateViewControllerWithIdentifier("SomethingViewController") as UIViewController
self.window?.rootViewController = viewController
But to be honest, it doesn't seem good to make the user wait with no information while you do the connection tests.
It would be better to show a temporary view controller during the waiting, unless those tests are really fast :)
Edit: corrected the code.
It's a hackish solution, as it will load a new rootViewController after initializing the original one I suppose. But it should work.
A more correct alternative would be creating the project as "empty application" and loading the storyboard by code, directly in the controller you want.

Resources