Issue in presenting view controller after pushing another view controller - ios

I have three view controllers -> A, B, C; A being the initial view controller.
Due to some requirement, i am pushing B view contoller over A view contoller.
After some processing, I am also presenting C view contoller over A view controller.
But if i am going to B in the time the processing happens, C view controller is presenting on B view controller.
Instead, i want when i go back from B, then C should be visible over A view controller.
I am doing the presentation and pushing programatically.
I tried to present C view controller over A ->
let navVC = UINavigationController(rootViewController: C)
navVC.modalPresentationStyle = .overFullScreen
A.present(C, animated: true, completion: nil)
i want C to present on A only, instead it is getting presented over B.

You have to pop() your B controller, and only then present() your C controller

Related

How to close multiple viewcontrollers in one time?

For my requirement, I have to go back to my rootviewcontroller (tabbar) but It have many page present on it.
example flow
my tabbar (have 4 tabs each tab has own navigation ) -> push(vc1) -> present nav(vc2) -> push vc3 -> present nav(vc4)
If I want to close all viewcontroller ( vc1 - vc4 ) How to dismiss them by one function ?
You can use UIViewController.dismiss(animated:completion:) call on the base view controller that started presentation from tab bar (root level).
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.
Given this hierarchy
my tabbar (have 4 tabs each tab has own navigation ) -> push(vc1) -> present nav(vc2) -> push vc3 -> present nav(vc4)
You can walk back the presentation hierarchy like following -
let vc4Presenter = vc4.navigationController?.presentingViewController
let vc2NavCtrl = (vc4Presenter as? UINavigationController) ?? vc4Presenter?.navigationController
let vc2Presenter = vc2NavCtrl?.presentingViewController
let vc1NavCtrl = (vc2Presenter as? UINavigationController) ?? vc2Presenter?.navigationController
vc2Presenter?.dismiss(animated: true, completion: {
vc1NavCtrl?.popToRootViewController(animated: false)
})
Above is merely an example of how you can find the correct view controller instance to call dismiss on in the view hierarchy. This is definitely not well suited for dynamic number of presentation layers.
You can -
Write this in a recursive way (so that it keeps looking for presentingViewController until it finds nil for the root level).
Have a convenient reference to tab bar controller throughout the app and call dismiss on it's currently selected view controller (tab).
If you want go directly to first viewController:
self.popToRootViewController(animated: true)
If you want to see how it is going away one by one
#objc func buttonPressed() {
let viewControllers: [UIViewController] = self.viewControllers
for aViewController in viewControllers {
self.popViewController(animated: true)
}
self.popToRootViewController(animated: true)
}

self.dismiss does nothing on 3rd level segue

I have a view controller embedded in a navigation controller. I segue to a view controller via a "show" segue. From there I navigate to a tableviewcontroller via a "show" segue. The navigation bar shows up with the back button but upon didclickrow self.dismiss does nothing. The completion block never executes. I'm baffled. There has to be some rule I don't understand about view controllers.
self.dismiss(animated: true, completion: {
print( "THIS NEVER EXECUTES AND NOTHING HAPPENS" )
})
The "show segue" is used to push a view controller.
So your code will not work as you are trying to dismiss the view controller that was not presented.
You should dismiss only when a view controller is presented or you have used a "Present Modally Segue" type.
You should use popViewController when you have used "Push Segue" type or have pushed a view controller.
self.navigationController?.popViewController(animated: true)
While in a UINavigationController, You should use the function
_ = navigationController?.popViewController(animated: true)
Or if you want to go back to the very top level view, use:
_ = navigationController?.popToRootViewController(animated: true)
The Apple Documentation for this function explains:
This method removes the top view controller from the stack and makes the new top of the stack the active view controller. If the view controller at the top of the stack is the root view controller, this method does nothing.

Swift - How to dismiss all of view controllers to go back to root

I want a my app can go to a first view controller when every time users want it.
So I want to create a function to dismiss all the view controllers, regardless of whether it is pushed in navigation controllers or presented modally or opened anything methods.
I tried various ways, but I failed to dismiss all the view controllers certainly.
Is there an easy way?
Try This :
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
it should dismiss all view controllers above the root view controller.
If that doesn't work than you can manually do that by running a while loop like this.
func dismissViewControllers() {
guard let vc = self.presentingViewController else { return }
while (vc.presentingViewController != nil) {
vc.dismiss(animated: true, completion: nil)
}
}
It would dismiss all viewControllers until it has a presentingController.
Edit : if you want to dismiss/pop pushed ViewControllers you can use
self.navigationController?.popToRootViewController(animated: true)
Hope it helps.
If you are using Navigation you can use first one
or if you are presenting modally you can second one:
For Navigation
self.navigationController?.popToRootViewController(animated: true)
For Presenting modally
self.view.window!.rootViewController?.dismissViewControllerAnimated(false, completion: nil)
Hello everyone here is the answer for Swift-4.
To go back to root view controller, you can simply call a line of code and your work will be done.
self.view.window?.rootViewController?.dismiss(animated: true, completion: nil)
And if you have the splash screen and after that the login screen and you want to go to login screen you can simply append presentedviewcontroller in the above code.
self.view.window?.rootViewController?.presentedViewController!.dismiss(animated: true, completion: nil)
Simply ask your rootViewController to dismiss any ViewController if presenting.
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.window?.rootViewController?.dismiss(animated: true, completion: nil)
(appDelegate.window?.rootViewController as? UINavigationController)?.popToRootViewController(animated: true)
}
The strategy to go back to your initial view controller could vary depending on your view controllers are stacked.
There could be multiple scenarios and depending on your situation, you can decide which approach is the best.
Scenario 1
Navigation controller is set as the root view controller
Navigation controller sets View Controller A as the root
Navigation controller pushes View Controller B
Navigation controller pushes View Controller C
This is a straightforward scenario where navigationController?.popToRootViewController(animated:true) is going to work from any view controller and return you back to View Controller A
Scenario 2
Navigation controller is set as the root view controller
Navigation controller sets View Controller A as the root
View Controller A presents View Controller B
View Controller B presents View Controller C
This scenario can be solved by the answers above
self?.view.window?.rootViewController.dismiss(animated: true) and will bring you back to View Controller A
Scenario 3
Navigation controller 1 is set as the root view controller
Navigation controller 1 sets View Controller A as the root
Navigation controller 1 pushes View Controller B
View Controller B presents Navigation Controller 2
Navigation Controller 2 sets View Controller D as the root
Navigation controller 2 pushes View Controller E
Now imagine that you need to go from View Controller E all the way back to A
Using the 2 answers above will not solve your problem this time as popping to root cannot happen if the navigation controller is not on the screen.
You might try to add timers and listeners for dismissing of view controllers and then popping which can work, I think there was an answer like this above with a function dismissPopAllViewViewControllers - I notice this leads to unusual behavior and with this warning Unbalanced calls to begin/end appearance transitions for
I believe what you can do to solve such scenarios is to
start by presenting your modal views controllers from the navigation controller itself
now you have better control to do what you want
So I would change the above to this architecture first:
Navigation controller 1 is set as the root view controller (same)
Navigation controller 1 sets View Controller A as the root (same)
Navigation controller 1 pushes View Controller B (same)
Navigation controller 1 presents Navigation Controller 2 (change)
Navigation Controller 2 sets View Controller D as the root (same)
Navigation controller 2 pushes View Controller E (same)
Now from View Controller E, if you add this:
let rootViewController = self?.view.window?.rootViewController as? UINavigationController
rootViewController?.setViewControllers([rootViewController!.viewControllers.first!],
animated: false)
rootViewController?.dismiss(animated: true, completion: nil)
you will be transported all the way back to View Controller A without any warnings
You can adjust this based on your requirements but this is the concept on how you can reset a complex view controller hierarchy.
Use this code for dismiss presented viewcontrollers and pop to navigation rootviewcontroller swift 4
// MARK:- Dismiss and Pop ViewControllers
func dismissPopAllViewViewControllers() {
if let appDelegate = UIApplication.shared.delegate as? AppDelegate {
appDelegate.window?.rootViewController?.dismiss(animated: true, completion: nil)
(appDelegate.window?.rootViewController as? UINavigationController)?.popToRootViewController(animated: true)
}
}
Swift 5.4:
self.navigationController?.popToRootViewController(animated: true)
Pops all the view controllers on the stack except the root view controller and updates the display.
func popToRootViewController(animated: Bool)
But if you want to go to specific controller just use the below function.
func popToViewController(UIViewController, animated: Bool)
Pops view controllers until the specified view controller is at the top of the navigation stack.
To achieve what you want, modify your navigation stack, then do popViewController.
let allControllers = NSMutableArray(array: navigationController!.viewControllers)
let vcCount = allControllers.count
for _ in 0 ..< vcCount - 2 {
allControllers.removeObject(at: 1)
}
// now, allControllers[0] is root VC, allControllers[1] is presently displayed VC. write back to nav stack
navigationController!.setViewControllers(allControllers as [AnyObject] as! [UIViewController], animated: false)
// then pop root VC
navigationController!.popViewController(animated: true)
See this for the way to further manipulate the navigation stack. If your topmost VC is modal, dismiss it first before the code above.
Create an Unwind Segue (You can find it at https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/UsingSegues.html copyright of Apple Inc.)
Unwind segues let you dismiss view controllers that have been
presented. You create unwind segues in Interface Builder by linking a
button or other suitable object to the Exit object of the current view
controller. When the user taps the button or interacts with the
appropriate object, UIKit searches the view controller hierarchy for
an object capable of handling the unwind segue. It then dismisses the
current view controller and any intermediate view controllers to
reveal the target of the unwind segue.
To create an unwind segue
Choose the view controller that should appear onscreen at the end of an unwind segue.
Define an unwind action method on the view controller you chose.
The Swift syntax for this method is as follows:
#IBAction func myUnwindAction(unwindSegue: UIStoryboardSegue)
The Objective-C syntax for this method is as follows:
- (IBAction)myUnwindAction:(UIStoryboardSegue*)unwindSegue
3. Navigate to the view controller that initiates the unwind action.
Control-click the button (or other object) that should initiate the unwind segue. This element should be in the view controller you want to dismiss.
Drag to the Exit object at the top of the view controller scene.
https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/Art/segue_unwind_linking_2x.png
Select your unwind action method from the relationship panel.
You must define an unwind action method in one of your view controllers before trying to create the corresponding unwind segue in Interface Builder. The presence of that method is required and tells Interface Builder that there is a valid target for the unwind segue.
In case anyone looking for an Objective-C implementation of the question's answer,
[self.view.window.rootViewController dismissViewControllerAnimated:true completion:nil];
func dismiss_all(view: UIView){
view.window!.rootViewController?.dismiss(animated: true, completion: nil)
}
May be what you are looking for is unwind segue.
Unwind segues give you a way to "unwind" the navigation stack back
through push, modal, popover, and other types of segues. You use
unwind segues to "go back" one or more steps in your navigation
hierarchy.
Link to documentation:
https://developer.apple.com/library/archive/technotes/tn2298/_index.html
The best and prefered way to do this is to create an unwind segue. Just follow this documentation https://developer.apple.com/library/archive/featuredarticles/ViewControllerPGforiPhoneOS/UsingSegues.html. It can de done in code or through the interface builder.

Dismiss a presented view controller that has been presented by a presented view controller [duplicate]

This question already has answers here:
Dismissing modal view controller stack
(3 answers)
Closed 5 years ago.
I am presenting a view controller with a presented view controller.
let viewController = self.getResponseViewController(r: r)
if let presented = self.presentedViewController {
presented.present(viewController, animated: true)
} else {
self.present(viewController, animated: true)
}
In the top level view controller, I have the following code...
presentingViewController?.presentingViewController?.dismiss(animated: true)
This works ok apart from the fact that the top level view controller immediately disappears and it's the first presented view controller that is animated out.
If I merely do...
dismiss(animated: true)
... then only the top most view controller is dismissed, leaving the first presented controller still in place.
How can I have the top level view controller animate out to reveal the root view controller, whilst showing no sign of the first presented view controller?
Basically, I just need to be able to stack two levels deep.
So A presented B and B is now presenting C.The easiest way to go to A, also as we don't want to see B exit, is to keep a reference to A and say dismiss or Pop to root on it from anywhere in C.OrDismiss C and in completion handler dismiss B

Presenting view controller with overCurrentContext then pushing to navigation stack results in navigation bar underlapping

I'm hitting my head against the wall with a navBar issue. See this sample project for a better idea of what I'm trying to achieve. Basically, my app structure is like this:
NavController -root-> ViewControllerA -> button -> push -> ViewControllerB --> ViewControllerC
| |
button -> presentModally |
| |
V |
PopoverViewController |
| |
button -> push to the nav controller |
| |
| |
---------------------------------------
A button on the second view controller (call it B) of the nav stack will present a modal view controller with a clear background (its modal presentation style is overCurrentContext to look like a popover):
Then, a button on the popover will push a new view controller on to the nav stack (call it C):
[
The popover is presented by B as opposed to the actual nav controller (B defines the presentation context). This so that when the popover pushes C on the stack, C doesn't just cover B, but covers the popover as well.
The problem arises when dismissing C. The nav stack pops back to B with the popover still on top (which is my intention). However, B now fills the entire frame of the nav view controller (before C was pushed, B's top was pinned to the navBar). This causes views near the top of B to be clipped by the navBar:
A look at the UI Inspector confirms that this is because B's view now fills the nav controller's view:
This was not the case before C was presented. Any ideas as to what's causing this to happen?
You are manually pushing a view controller onto the stack which doesn't have a navigation controller. A better way to do it would be to segue from view controller B to view controller C using a segue identifier. Once you setup the segue in the storyboard you will see that view controller C gets a nav bar automatically. You might want to use a delegate method from the popover view controller to B to trigger the segue.
I had a problem very similar to this one. I was presenting a view controller using a .overCurrentContext presentation mode. When the viewController was displayed the navigation was over it!
To solve the problem I just asked to the navigation show the new view controller instead of the old viewController.
viewController.navigationController?.present(navigationController, animated: true, completion: nil)
Doing by this way the viewController will not be behind the navigation anymore. I hope it helps someone.
swift 5
I did this way:
let vc = viewControllerToDisplay()
let navVc = UINavigationController(rootViewController: vc)
navVc.modalPresentationStyle = .overCurrentContext
navVc.modalTransitionStyle = .crossDissolve
navigationController?.present(navVc, animated: true, completion: nil)

Resources