isBeingPresented value is inconsistent - ios

I present a NavigationController with a ViewController in it modally.
In the ViewController I can see that self.navigationController.isBeingPresented is true.
But if I now push a new ViewController on the modally presented NavigationController and pop back to the original ViewController the same call to check isBeingPresented returns false.
Documentation is sparse but I can't really explain this inconsistency other than that it may be a bug?

That's the intended behavior.
isBeingPresented is true only when the given viewController is currently being presented (docs):
A Boolean value indicating whether the view controller is being presented.
and not when it is already presented. It is set to true during the presentation process - from the point when navigation to that view controller starts until the moment when the view controller is fully presented, and all the lifecycle events happened (presentation animations finished, viewWillAppear/viewDidAppear callbacks were called, etc.). After that moment, the view controller is presented, but not is being presented, thus the isBeingPresented will not be set to true anymore.
The self.navigationController was presented at first (by modal presentation), popping a view controller from it does not trigger a presentation. After presenting a UINavigationController, it is presented whole time during pushing and popping view controllers on it. You would have to dismiss the navigationController, and then present it again for the isBeingPresented to be true - because only during modal presentation it is being presented.

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

Initial View Controller from everywhere

I got an application with a navigation view controller as Initial View Controller. After loading the initial view controller, I set up a notification listener. The Notification can be posted everywhere in the app. I have some pushed vc's and also modally presented. My goal is to return to the initial vc and present a modal view controller from there if the notification is triggered but I have no Idea how to do that. Do I need to do this outside of the MainViewController?
Answer assumes rootViewController is UINavigationController as specified by OP in his question
You can achieve what you want using
(UIApplication.shared.keyWindow?.rootViewController as! UINavigationController).dismiss(animated: true) {
(UIApplication.shared.keyWindow?.rootViewController as! UINavigationController).popToRootViewController(animated: true)
}
Whats happening is pretty simple. Knowing that your initial viewCOntroller is always UINavigationController, initially check if you have anything presented on rootView controller, if yes dismiss it and in the completion block pop to rootViewController of your initial viewController.
Hope it helps

Does using `self.navigationController?.popViewController(animated: true)` dismiss the current ViewController or just return to the previous?

I was wondering if self.navigationController?.popViewController(animated: true) actually dismisses the View Controller it is being called on or if it just returns to the previous? If so, does this mean that I must manually dismiss the View Controller along with using the above code? Thanks.
When you pop a view controller off a UINavigationController's navigation stack, the UINavigationController releases the popped view controller and by default it goes out of existence. You can see this by implementing that view controller's deinit.
If you don't see deinit called, then that view controller is leaking because you have a retain cycle, and you need to worry about why that is.

Getting current visible modalViewController from appdelegate

I have a navigation controller which acts as an rootViewController and I have one view controller which is presented modally (not pushed). So my control reaches app delegate while this presented viewController is the one, which is visible. So my question is, how can I get this viewController in appDel? And, No, its not my rootViewController and is not present in navigation stack.
I believe yo may use presentedViewController property of rootViewController. Check this answer for more details: https://stackoverflow.com/a/12684721/1301013

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

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.

Resources