Dismiss all modals in iOS with Swift 4 - ios

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})

Related

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

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

Who should be the one dismissing the view controller?

I wanted to ask who should be the one dismissing a presented view controller?
Lets say I presented a view controller and on an IBAction in that view controller, I want to dismiss it.
Should I be passing that responsibility to the presenting view controller by creating a delegate method or I should be just calling the dismissViewController:animated: on itself, which inturn anyways asks its presenting view controller to dismiss the presented view controller?
So, I think these are some clear cut cases where the presenting view controller should be the one dismissing the presented view controller
The presented view controller is passing some data back to the presenting view controller.
The presenting view controller wants to do something after the dismissal of the presented view controller.
The presenting view controller to handle how the dismissal is going to happen, does it need some kind of animation
What if the presented view controller first checks if the presenting view controller actually wants to take the responsibility of dismissal by checking the presenting view controller implemented the dismissal delegate method?
Is it really worth putting the complexity of conditional logic here?
And yes, I tried reading it on other forums and questions like
Dismissing a Presented View Controller
Dismissing Modal View Controllers
Present and dismiss modal view controller
view controllers: presentation, dismissal
But couldn't really find the right logical answer.
Read this below link. You will get the idea how animation and presentation take place between viewcontrollers.
https://www.raywenderlich.com/113845/ios-animation-tutorial-custom-view-controller-presentation-transitions

iOS Swift - Using temporary UIView that can be cancelled

I have a View Controller that is attached to a Navigation Controller (so any segue out of the View Controller maintains the view hierarchy of the Navigation Controller). How would I go about creating a temporary view that pops up from below that can be cancelled and does not conform to the Navigation Controller. For example, the 'Add Event' button in the Apple Calendar App, which brings up a screen that can be cancelled and brought back down.
You can use a UIPopoverController or you can just make a custom UIViewController and add it to the current view controller as a child view controller and present it modally without interfering with the Navigation controller.

What happens with presenting viewController after it presents a presented viewController?

What happens with presenting viewController after it presents a presented viewController. Does its instance live forward? Is it destroyed when it is obscured by presented viewController views? If it is destroyed, how do I tell the system not to destroy it, but keep it intact?
I'm designing a game in witch the game is obscured with a presented viewController when some button is clicked. When the player returns to the game, I want it to restore its state exactly as it was before presenting a viewController
The presenting view controller keeps it's instance, and control is returned to it after the presented view controller is dismissed. In fact, you should have the presenting view controller dismiss the presented controller if possible.
Since the instance of UIViewControllers stay intact when presenting (or pushing) another view controller, it is best practice to implement the didReceiveMemoryWarning method in your UIViewControllers to release any memory that you can (clear caches, etc). Any data you clear out you may want to re-populate in the viewWillAppear method.
Other info from the View Controller Programming Guide for iOS :
Support for presenting view controllers is built in to the UIViewController class and is available to all view controller objects. You can present any view controller from any other view controller, although UIKit might reroute the request to a different view controller. Presenting a view controller creates a relationship between the original view controller, known as the presenting view controller, and the new view controller to be displayed, known as the presented view controller. This relationship forms part of the view controller hierarchy and remains in place until the presented view controller is dismissed.

dismiss a modally presented view controller to a different underlying view controller

I have a UIViewController that's embedded in a Navigation View Controller. I then modally present another view controller that shows a countdown. Once the countdown ends, the modal view controller should be dismissed and show a different underlying view controller from the original presenting UIViewController.
Does anyone know how to do this in ios8 with Swift?
There are different ways to approach this. One way is to replace the initial presenting view controller with the desired underlying one when you present the modal view controller.
NSArray * viewControllers = [self.navigationController viewControllers];
[viewControllers replaceObjectAtIndex:viewControllers.count - 1 withObject:replacementController];
Dismissing the modal will simply show the different underlying view controller that was already swapped.
when you do popViewController from stack there is a handler. This will be called at the end of this function so you can pass a block where you can presentViewController or make change to the current one.

Resources