Is there a way to know if a UIViewController has been presented and dismissed modally ? - ios

Is there a way to know if a UIViewController has been presented and dismissed modally ?
Something like:
hasBeenPresentedModally
hasBeenDismissedModally
thanks

There's nothing built in, but a view controller could, upon receiving viewDidAppear and/or viewWillDisappear check whether it has a parentViewController, since per Apple's documentation (emphasis added):
Parent view controllers are relevant in navigation, tab bar, and modal
view controller hierarchies. In each of these hierarchies, the parent
is the object responsible for displaying the current view controller.
If you are using a view controller as a standalone object—that is, not
as part of a view controller hierarchy—the value in this property is
nil.
If it has then it can set suitable flags for future reference.
Note that being presented modally is different from being truly modal. For example, on an iPad you might put one controller inside a UIPopoverController, so that controller isn't presented modally, but then it might modally present another controller on top of itself. So the second controller is presented modally but isn't itself a modal dialogue because — if the program is otherwise set up suitably — the user can just ignore the popover entirely.

Check if your UIViewController's parentViewController property is nil or not.
If the property is nil then it's dismissed otherwise it's presented.
NOTE: UITableViewController's childViewController's parentViewController property would also be not nil, you should also make sure the parentViewController is not UITableViewController.

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

Presenting stack of view controllers

Is there a way to present two view controllers at once? My scenario is the following:
From my root view controller (View 0), I want to modally present a view (View 1), and when dismissing that view, a new view (View 2) should be beneath it.
The view hierarchy would look like so:
- View 1
- View 2
- View 0 (root)
I tried presenting it without animations in -viewDidAppear in View 1. This does not seem to be called until the presentation animation is finished, though. And presenting it before -viewDidAppear wouldn't work since the view is not yet in the window hierarchy.
Are there any acceptable ways of doing this?
Presenting a UIViewController while another UIViewController is presenting is not possible without stability issues. As a user experience concern, I think that approach would also not be the best. They should know what to expect when they dismiss a view controller.
If you aren't hard set on using presentViewController: and you have a UINavigationController, instances of that class have a method setViewControllers:animated: that allow you push two UIViewControllers onto a stack. The user would be able to see what they get when they go back (the back item).
Otherwise, you can present on VC, and immediately on success present the other. That's the safest option of doing what you want.

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.

source view controller vs. presenting view controller

I'm reading a book that states that source view controller is not necessarily a presenting view controller. The source VC is the one that calls presentViewController:... method, and the presenting VC(it's view) is the one that gets obscured by a presented VC view. I can't think of a single example in which the presenting VC is not the same as source VC. Please provide some. Thanks
Text from book:
“Original presenter:
The view controller to which presentViewController:animated:completion: was sent. Apple sometimes refers to this view controller as the source; “original presenter” is my own term.
The presented view controller is set as the original presenter’s presentedViewController.
Presenting view controller:
The presented view controller’s presentingViewController. This is the view controller whose view is replaced or covered by the presented view controller’s view. By default, it is the view controller whose view is the entire interface — namely, either the root view controller or an already existing presented view controller. It might not be the same as the original presenter.
The presented view controller is set as the presenting view controller’s presentedViewController. Thus, the presented view controller might be the presentedViewController of two different view controllers.”
I was asking myself the same question when learning about view controller transitions, specifically when trying to understand the animationControllerForPresentedController:presentingController:sourceController: method from the UIViewControllerTransitioningDelegate protocol. The struggle was about the difference between the presentingController and sourceController arguments.
I found the answer in the View Controller Programming Guide. You can find the following in the Presenting View Controllers Modally section:
The view controller that calls the
presentViewController:animated:completion: method may not be the one
that actually performs the modal presentation. The presentation style
determines how that view controller is to be presented, including the
characteristics required of the presenting view controller. For
example, a full-screen presentation must be initiated by a full-screen
view controller. If the current presenting view controller is not
suitable, UIKit walks the view controller hierarchy until it finds one
that is. Upon completion of a modal presentation, UIKit updates the
presentingViewController and presentedViewController properties of the
affected view controllers.
This means that the 'source' view controller concept is not something randomly created by the author of the book you mentioned.
I'm afraid the book you're reading may have made a complex subject even more complex by adding the so-called Source View Controller to the whole mix of names.
For starters, there is no such concept of a "Source" in View Controllers. You have a parentViewController and childViewControllers only when talking about Container View Controllers. And you have a presentingViewController and a presentedViewController only when talking about presenting View Controllers modally.
You also have View Controllers whose main purpose is to manage other View Controllers, namely the Navigation Controller, the Tab Bar Controller, the Split View Controller and the Popover Presentation Controller. So any given View Controller may query itself to know if it is "attached" to a Navigation Controller, for instance.
What I'm guessing your book is implying is that a Source VC would be the one that makes another VC appear on screen. In this sense, it is right. The "source" may be different than the presenting VC. Take this snippet:
[self presentViewController:aViewController animated:YES completion:nil];
In the example above, the presenting VC is also the "source" VC. But here:
[someViewController presentViewController:anotherViewController animated:YES completion:nil];
We are calling the method on some VC lying somewhere passing yet another VC as argument. So in this case the presenting VC is someViewController and the presented VC is anotherViewController. Should this line of code be inside a third VC, then said third VC would be the "source".
But that's the thing! This last snippet of code may not even be inside a View Controller in the first place, so it's a bit odd to think about "source VCs".

Adding Popover to current Navigation Controller hierarchy

I've seen a lot of other questions on here about adding a UINavigationBar to a UIPopoverController. All of the examples I've seen follow one of two patterns:
In the init or viewDidLoad method of the Popover subclass, you alloc-init a UINavigationBar directly, as suggested here. This method is a little hacky, and while it shows up nicely, if the popover is a UITableViewController, you have to mess with a bunch of things to make sure the navigation bar you just added doesn't overlap one of your cells.
Alternatively, a lot of post suggest creating a UINavigationController just before presenting the popover, as shown here.
With the second method, however, won't the popover be the only controller in the newly created navigation controller? And if my view that I'm presenting the popover from is itself already in a navigation controller, the popover will NOT be in that same navigation controller, correct? It seems to be that the more appropriate thing to do would be to add the popover being created as another controller in the navigation controller that already exists (and which the controller that presents the popover is already a part of). Is that possible? Or is there a reason why the navigation controller for the popover needs to be independent from the navigation controller for the presenting controller? Or am I totally missing something here?
You have many questions, young Skywalker. :)
Creating a UINavigationController and then embedding the controller you would like to present is the way to go.
Don't get confused by all the controllers involved here:
UIPopoverController is a construct that shows an existing UIViewController in an overlay like style. UIPopoverController itself even isn't a subclass of UIViewController. The name is misleading.
So UIPopoverController hosts another controller. In your case, we let it host a UINavigationController.
UINavigationController is a subclass of UIViewController. It is a container controller and can handle a stack of UIViewControllers.
On that stack we push one UIViewController: the one you want to display and garnish with a UINavigationBar. Since Mr. UINavigationController comes with a build in UINavigationBar, he's our friend.
There is no need to subclass UIPopoverController. You just keep one static reference to it around so you can dismiss the current open popover in case you want to present another.
It does not matter where you present the UIPopoverController from. It will always be a popover. Even if presented from an existing UINavigationController. Only if you use presentViewController: you will get different results depending on the controller you're presenting from (modal or pushed on top of the stack).
won't the popover be the only controller in the newly created navigation controller?
No, the popover will contain the navigation controller and the navigation controller will only contain its root view controller (which would otherwise have been added directly to the popover as its root).
You seem to be a little confused about the relationship between the popover and the popover root view controller...
the popover will NOT be in that same navigation controller, correct
Yes, correct. The popover is effectively a window floating above all other views
Or am I totally missing something here?
Maybe... The popover would usually be used for displaying something modal, transient and smaller than full screen size. Putting a navigation controller in the popover and adding views to it is the normal approach.
Adding a navigation bar to a popover isn't hacky. A navigation bar is just another regular view. That also means that using a UITableViewController with it, the navigation bar will overlap the table view, as the UITableViewController's view property just returns the controller's tableView property. If you want to add a navigation bar above a table view, without it overlapping the table view, use a regular UIViewController and add your navigation bar and table view the normal way. UITableViewController should only be used if your only view within that view controller is a table view.
Having said that, I do agree with others that just using a navigation controller without using its navigation features is the most common approach.

Resources