How to dismiss all but one view controllers and pop to specified view controller in Swift? - ios

I have a bunch of views and then a logout button, which logs the user out and takes them to the first view controller (a login/register screen). I tried doing this with a modal presentation, but it destroys my navigation, and I can't use a pop to root view controller because it is not the root view controller - I am at least 2 navigation controllers deep. How would I go about somehow displaying only the first one? I basically need it to act as if the app was just relaunched. Would unwind segues help in some way? Thanksthis is what I mean by messing up the navigation. The following view controllers now pop up, instead of (the following pic is from the actual first time launching the app) how it should look

Supposing that you use a UINavigationController, you can use the UINavigationController.setViewControllers(_:animated:) method in combination with UINavigationController.viewControllers. The latter provides you an array of view controllers in the exact order they are stacked by the navigation controller, while the former can be used to alter the navigation stack. For instance, if you want to keep only the first 3 view controllers in the navigation stack:
guard let navigationController = self.navigationController else { return }
let viewControllersToKeep = navigationController.viewControllers.prefix(3)
navigationController.setViewControllers(viewControllersToKeep, animated: true)
Note: The animation performed by the navigation controller in this case is usually (I believe always, but not 100% sure) a push animation instead of pop, which might not be the desired one.
So if you want to make the navigation controller to perform a pop animation, then you should call pop until you reach the desired view controller, but the key is to animated a single pop. For the same example as above:
guard let navigationController = self.navigationController else { return }
let numberOfPops = navigationController.viewControllers.count - 3
for i in 0..<numberOfPops {
let animated = i == 0
navigationController.popViewController(animated: animated)
}
Hope this answers the question about popping to the desired view controller (you can detect your view controller by its type, then you can find out its index and use the logic above).
If you want to dismiss to the first presenting view controller, then you need to call UIViewController.dismiss(animated:completion:) on the first presenting view controller. dismiss method will behaves like this:
If the view controller is presenting a view controller, then it will dismiss the presented view controller;
If the view controller is not presenting any view controller, but it is presented by another view controller (i.e. has a parent view controller), then it will ask the parent view controller to dismiss it;
If none of the above, it will do nothing.
So long story short, you need to call dismiss on the view controller that you want to be left on the screen.

I basically need it to act as if the app was just relaunched.
In this case, assigning the login/register screen controller to UIWindow.rootViewController seems to be the right option. This topic has been already covered here: Swift ios set a new root view controller

Related

Dismiss all modals in iOS with Swift 4

I am trying to achieve a navigation similar to the Netflix app for iOS. When you click on a movie, a modal window pops up with a close button. If within this movie I choose to see another movie then the second modal pops up and in addition to the close button, a back button appears. I can use the back button to dismiss one by one and the close button to return to the base screen.
I am able to dismiss a single view using
dismiss(animated: true, completion: nil)
but how can I return to the base screen closing all modals at once? Also, is modals the way to go? I chose this because I didn't want the navigation bar on top.
I'm working with Swift 4.2 in Xcode 10.
The way you are dismissing a ViewController is not the correct way. The presenting view controller is responsible for dismissing the view controller. Ideally you have to implement a protocol in your presenting ViewController and , dismiss your modal from your 'presenting' ViewController not 'presented' ViewController.
The reason why your way still works is, when a ViewController calls self.dimiss if there's nothing to dismiss UIKit will delegate it back to its parent. If you implement this correct way, once you dismiss , your presenting viewcontroller will dismiss , hence all the presented viewcontrollers will be dismissed instead of the last one.
From Apple Docs:
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, UIKit asks the presenting view controller to handle the dismissal.
If you present several view controllers in succession, thus building a stack of presented view controllers, calling this method on a view controller lower in the stack dismisses its immediate child view controller and all view controllers above that child on the stack. When this happens, only the top-most view is dismissed in an animated fashion; any intermediate view controllers are simply removed from the stack. The top-most view is dismissed using its modal transition style, which may differ from the styles used by other view controllers lower in the stack.
If you want to retain a reference to the view controller's presented view controller, get the value in the presentedViewController property before calling this method.
The completion handler is called after the viewDidDisappear(_:) method is called on the presented view controller.
try this
self.navigationController?.viewControllers.removeAll(where: {$0.isModalInPopover})

Push and present view controllers with a single push animation

From my navigation controller's root view controller Root
I want to push a view controller A
which then instantaneously presents another view controller B.
How can I do both at the same time with a single push animation?
(💡 The idea behind this is that view controller A allows for editing some content. If no content has been created, it needs to show view controller B first which allows the user to enter a title and then create the content.)
What I've tried:
When I do the following in view controller A's viewDidLoad() method:
if content == nil {
let createContentViewController = // instantiate new view controller instance
present(createContentViewController, animated: false)
}
UIKit also omits the push animation when animated is set to false – so I get no animation at all. When animated is set to true, I get a double animation (first push, then modal). 🙄
The reason you're having trouble doing what you describe is that you can't present View Controller B on View Controller A until View Controller A is part of the view controller hierarchy — and until you have pushed it, it isn't.
I would suggest, therefore, not using a presented view controller in this story at all. All you are really describing, it seems to me, is a View Controller A and its main view (View A) with a View B in front of it. That's something you can readily prepare between creating View Controller A and pushing it. View B can still contain a Dismiss button or similar to which you respond by sliding it off the screen, revealing View A.
If you really need a View Controller B for purposes of code organization, then have View Controller A be a custom parent view controller for View Controller B. A cool feature of that solution is that the whole thing can be configured in the storyboard using an embed segue. If we push View Controller A and you don't need View Controller B, you just hide View B before pushing.

Swift Using a Back Button on Two View Controllers

My second view controller is a table view controller. I wish to segue to a first view controller with a back button. I have tried using Editor>Embed In>Navigation Controller. It places a navigation bar on the top where I can place an Item (Back button) on it, and after segueing it back to my first view controller, it becomes very screwy (it shows two navigation bars on top of each other). Is there a better/easier way to move back to my first view controller?
if u want to use navigation controller to manage ur push&pop stuff, just make sure:
navigation controller is ur initial view controller
ur first view controller is the root view controller of navigation controller
set an unique identifier to second view controller
leave ur second view controller alone without any link to other vc
like this
then push like this
let secondVC = (self.storyboard?.instantiateViewControllerWithIdentifier("identifier"))! as! urVC
secondVC.title = "second view controller"
self.navigationController?.pushViewController(secondVC, animated: true)
and pop like this
self.navigationController?.popViewControllerAnimated(true)
alternatively, u can use unwind segue following this tutorial

Adding a Back Button on A Table View Controller

I am interested in implementing a back button to segue to a previous view controller. I have tried to embed a navigation bar onto the the top of the table view controller but the issue is when I do segue back to the first view controller, by default, Xcode keeps the navigation bar with a back button to go back to the table view controller. Is there a simpler way to implement a segue to go back to the first VC without the navigation bar remaining?
I'm not too sure if this works, but embed your view controllers including the first one inside the navigation controller. That would make all your view controllers with navigation bar above.
On the main view controller (the one you do not want to have the navigation bar), add the line of code inside your viewDidLoad method.
Swift 3:
self.navigationController?.navigationBar.isHidden = true
I found an easy way. On your TableViewController, drag a UIview to the top of the view controller and itll let you insert the view. From there just add your back button
Just assign your "Back" UIBarButtonItem to this method and it should work.
#IBAction func backButtonPressed(sender: AnyObject) {
self.dismissViewControllerAnimated(true, completion: nil)
}
Sounds like a few problems
1) How are you navigating from the first view controller? Is it with Show (Push) if not, you are replacing the first view controller in your stack.
2) Based on your problem, make sure your first view controller is not embedded in a navigation controller
3) As Dylan mentioned, you need to hook your back button to remove the current view controller to return to the first one
4) Extension of (3), how are you segueing back to the first view controller? If you are explicitly navigating to first view controller with your back button handler, it's not actually going back but rather forward and keep the navigation bar.

How to dismiss a NavigationController in Storyboard?

I've looked everywhere for this, so as a last resort I figured I should ask the question.
I'm using Storyboard in XCode and have a navigation controller as my initial view. The root view of this navigation controller is a table view. In this table view, I have a button in the navigation bar that flips the view horizontally via a modal segue to a navigation controller. The root view of this controller is my map view. Now, when I want to dismiss this map view using [self dismissViewControllerAnimated:YES completion:nil] it doesn't work. It does if I take out the navigation controller. So, I'm guessing the dismissViewController is getting passed to the navigation controller, and not doing anything. I know I could implement a class for the navigation controller to handle the call and pass it on, but I don't want to do that unless absolutely necessary.
So to solve this question:
I need a way to switch between table view to map view using the flip horizontally animation and vice versa.
I need to have a navigation controller in both views, I could either be the same one, or a different one. I can't seem to find a way to use the same one, since any transitions would be pushes which don't do the flip horizontally transition.
Thanks in advance for the help!
Who is self when you call [self dismissViewControllerAnimated:YES completion:nil]?
The receiver of this message needs to be the view controller which presented the modal view controller you want to dismiss. If you call it on the presented view controller, or (in the case of container view controllers like UINavigationController, one of the view controllers it manages), UIKit will try to do the right thing but won't always succeed.
Presuming the self you refer to is the view controller containing the map view, you can get the presentingViewController property to get the object you should call dismissViewControllerAnimated:completion: on. (And if you need to get the navigation controller that's in between, use the navigationController property.)

Resources