How to get the current visible viewController from AppDelegate - ios

So, I'm using the method bellow from UIApplication extension to get the top view controller:
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}
But the problem is: It always returns UIViewController. But I need to check if it is MyViewController for example. How do I achieve that?

Do conditional casting on the return value to safely check its type.
if let currentVC = UIApplication.topViewController() as? MyViewController {
//the type of currentVC is MyViewController inside the if statement, use it as you want to
}
Your whole function implementation is flawed, if it actually worked, it would lead to infinite recursion. Once you find out the type of your current top view controller in your if statements, you are calling the same function again with the current root controller as its input value. Your function only ever exists, if it reaches either a call from a view controller, whose class is none of the ones specified in your optional bindings.
Moreover, your whole implementation doesn't do anything at the moment. You find out the type of your root view controller, but than you upcast it by returning a value of type UIViewController.

You can do a conditional check with an if-let statement like this:
if let presented = controller?.presentedViewController as? MyViewController {
// it is a MyViewController
}
You can also just directly check if the UIViewController is that type of class like this:
if controller?.presentedViewController is MyViewController {
// it is a MyViewController
}

Try this:
if let presented = controller?.presentedViewController as? MyViewController {
...

You can check it in following ways
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
else if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
else if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}
// Answer
if let topVC = AppDelegate.topViewController() as? MyViewController {
// Here your topVC is MyViewController
}
// or
if let topVC = AppDelegate.topViewController() {
if topVC is MyViewController {
// Here your topVC is MyViewController
}
}

To use the UIViewController as MyViewController:
if let myViewController = UIApplication.topViewController() as? MyViewController { ... }
or if you just want to check that the UIViewController is of type MyViewController:
if UIApplication.topViewController() is MyViewController { ... }

Related

Check to specific VC in AppDelegate

I'm trying to check specific VC if app is running in foreground. My root view controller class is SWRevealViewController. After that I have a TabBarController and under it there is NavigationController and ViewController under it.
My heirachy is,
SWRevealViewController --> TabBar Controller --> Navigation Controller --> MessageVC --> ChatVC
I want to check in app delegate if app is on ChatVC or not if running on foreground.I have tried this code,
let tabBar:UITabBarController = self.window?.rootViewController as! UITabBarController
let navInTab:UINavigationController = tabBar.viewControllers?[1] as! UINavigationController
let storyboard = UIStoryboard(name: "Dashboard", bundle: nil)
let destinationViewController = storyboard.instantiateViewController(withIdentifier: "ChatDetailViewController") as? ChatDetailViewController
if destinationViewController?.restorationIdentifier == "ChatDetailViewController"
{
print("Yes")
}
else
{
print("No")
}
But app crashes with this error,
Could not cast value of type 'SWRevealViewController' (0x100dc4b20) to 'UITabBarController' (0x211b289f0).
How i can check if app is on ChatVC or not?
Screenshot of storyboard :
I have an extension for it
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
let moreNavigationController = tab.moreNavigationController
if let top = moreNavigationController.topViewController, top.view.window != nil {
return topViewController(base: top)
} else if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
USAGE:
if UIApplication.topViewController is YourViewController {
// do smth
}
I never used SWRevealViewController but you can try this.
As you said in your first line "SWRevealViewController is the RooVC" and you are converting the SWRevealViewController to UITabBarController (check below line of your code). This is your crash reason.
let tabBar:UITabBarController = self.window?.rootViewController as! UITabBarController
Now you need to change, UITabBarController to SWRevealViewController
let rootVC = self.window?.rootViewController as! SWRevealViewController
Now get all ViewControllers
if let navController = rootVC.navigationController { // for safety check
for controller in navController.viewControllers {
if controller is ChatVC {
print("Chat VC is available")
break
}
}
}
For safe coding and keeping swift optional binding in mind, you can do code like below,
Updated answer
var haveChatVC = false
if let rootVC = self.window?.rootViewController as? SWRevealViewController,
let tabbar = rootVC.frontViewController as? UITabBarController {
if let requiredNC = tabbar.viewControllers?[1] as? UINavigationController {
for vc in requiredNC.viewControllers {
if vc is ChatVC {
// ...
haveChatVC = true
break
}
}
}
else {
print("Navigation controller not found.")
}
}
else {
print("Unable to get root controller or navigation controller")
}
if haveChatVC {
// do task here when chat vc available
}
else {
// do task here when chat vc not available
}
Note: This is only pseudo code.

How to call popToRootViewController() function from a separate view controller?

This is the function that pops the user back to the root in a UINavigationController:
func popToRoot() {
navigationController?.popToRootViewController(animated: true)
}
However, I need to call this function from the tab bar, which is obviously in an entirely different class and file and is not even in the navigation stack.
func moveToTab3(sender: UIButton!) {
if CurrentContentController != containerStack[3] {
moveToTab(3, animated: false)
} else {
// since you are already in this tab, popToRoot function goes here, but how?
}
}
The problem is that .navigationController? is very particular and it appears has to be called from within the object it resides. How is this done?
I'm not sure that I got you right, but to make possible to navigate to the root from current controller, you can detect the very top controller in your app using this:
extension UIApplication {
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
return controller
}
}
Then you can access to that top controller and call popToRoot:
if let topController = UIApplication.topViewController() {
// navigate to the root
topController.navigationController?.popToRootViewController(animated: true)
}
It should work. Let me know if you need more info

I have multiple storyboards. How can I use AppDelegate to open another ViewController in another storyboard? (Segue)

Here is the code I have. I have tried a few different approaches and some of them gives me the error that the view is not in the hierarchy.
The code snippet below goes in the correct else but can't perform the segue or presentViewController
func applicationDidTimout(notification: NSNotification) {
if let vc = self.window?.rootViewController as? UINavigationController {
if let myTableViewController = vc.visibleViewController as? AccountsOverviewViewController {
// Call a function defined in your view controller.
myTableViewController.signOffUser()
} else {
// We are not on the main view controller. Here, you could segue to the desired class.
let storyboard = UIStoryboard(name: "Accounts", bundle: nil)
let vc = storyboard.instantiateViewControllerWithIdentifier("AccountsNavigationController") as UIViewController
let vc2 = getVisibleViewController(nil)
vc2?.presentViewController(vc, animated: true, completion: nil)
}
}
}
func getVisibleViewController(var rootViewController: UIViewController?) -> UIViewController? {
if rootViewController == nil {
rootViewController = UIApplication.sharedApplication().keyWindow?.rootViewController
}
if rootViewController?.presentedViewController == nil {
return rootViewController
}
if let presented = rootViewController?.presentedViewController {
if presented.isKindOfClass(UINavigationController) {
let navigationController = presented as! UINavigationController
return navigationController.viewControllers.last!
}
if presented.isKindOfClass(UITabBarController) {
let tabBarController = presented as! UITabBarController
return tabBarController.selectedViewController!
}
return getVisibleViewController(presented)
}
return nil
}
Use the func below to get the visible view controller,
func getVisibleVC() -> UIViewController? {
if var visibleVC = window?.rootViewController {
while let presentedVC = visibleVC.presentedViewController {
visibleVC = presentedVC
}
return visibleVC
}
return nil
}

Top most ViewController under UIAlertController

I am using the following extension to find the top most ViewController.
If alert is presented, the code above gives UIAlertController.
How do I get top view controller under UIAlertController?
Create an UIApplication extension like below and UIApplication.topViewController() will return the top most UIViewController under UIAlertController
iOS 13+
extension UIApplication {
class func topViewController(controller: UIViewController? = UIApplication.shared.windows.first?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
if let alert = controller as? UIAlertController {
if let navigationController = alert.presentingViewController as? UINavigationController {
return navigationController.viewControllers.last
}
return alert.presentingViewController
}
return controller
}
}
iOS 12-
extension UIApplication {
class func topViewController(controller: UIViewController? = UIApplication.shared.keyWindow?.rootViewController) -> UIViewController? {
if let navigationController = controller as? UINavigationController {
return topViewController(controller: navigationController.visibleViewController)
}
if let tabController = controller as? UITabBarController {
if let selected = tabController.selectedViewController {
return topViewController(controller: selected)
}
}
if let presented = controller?.presentedViewController {
return topViewController(controller: presented)
}
if let alert = controller as? UIAlertController {
if let navigationController = alert.presentingViewController as? UINavigationController {
return navigationController.viewControllers.last
}
return alert.presentingViewController
}
return controller
}
}
You can get the parent controller of UIAlertController using its presentingViewController property
extension UIApplication {
class func topViewController(base: UIViewController? = (UIApplication.sharedApplication().delegate as! AppDelegate).window?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let alert = base as? UIAlertController {
if let presenting = alert.presentingViewController {
return topViewController(base: presenting)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
return base
}
}
Use these changes in your code, Not tested on XCode.
You could check if the next viewController is UIAlertController and if so return its parent. Something like this:
if let presented = base as? UIAlertController {
return base.presentingViewController
}
Add this in the extension you use before return.
Updated
extension UIApplication {
class func topViewController(base: UIViewController? = (UIApplication.sharedApplication().delegate as! AppDelegate).window?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(base: nav.visibleViewController)
}
if let tab = base as? UITabBarController {
if let selected = tab.selectedViewController {
return topViewController(base: selected)
}
}
if let presented = base?.presentedViewController {
return topViewController(base: presented)
}
if let alert = base as? UIAlertController {
return alert.presentingViewController
}
return base
}
}
I used this extension to get the top most view controller under an UIAlertController, basically what I do is to stop looking for top view controller when I found one that is an UIAlertController.
extension UIApplication {
var topViewController: UIViewController? {
var viewController = keyWindow?.rootViewController
guard viewController != nil else { return nil }
var presentedViewController = viewController?.presentedViewController
while presentedViewController != nil, !(presentedViewController is UIAlertController) {
switch presentedViewController {
case let navagationController as UINavigationController:
viewController = navagationController.viewControllers.last
case let tabBarController as UITabBarController:
viewController = tabBarController.selectedViewController
default:
viewController = viewController?.presentedViewController
}
presentedViewController = viewController?.presentedViewController
}
return viewController
}
}
I think you want to push a new VC on current top visible VC which is a UIAlertController, then this UIAlertController will disappear immediately, cause pushed new VC dismiss too. Finally, you can not push a new VC.
The problem is, if you new a UIAlertView, then call show, Cocoa Touch will initialize a new window which rootViewController is UIApplicationRotationFollowingController which presentingViewController is UIAlertController. So you cannot traverse the top most VC under UIAlertController because it exist in another window!
So if topViewController traverse from keyWindow?.rootViewController, find a UIAlertController, call topViewController again but traverse from window what you want, such as (UIApplication.sharedApplication().delegate as! AppDelegate).window?.rootViewController
This is the correct one:
func firstApplicableViewController() -> UIViewController? {
if (self is UITabBarController) {
let tabBarController = self as? UITabBarController
return tabBarController?.selectedViewController?.firstApplicableViewController()
} else if (self is UINavigationController) {
let navigationController = self as? UINavigationController
return navigationController?.visibleViewController?.firstApplicableViewController()
} else if (self is UIAlertController) {
let presentingViewController: UIViewController = self.presentingViewController!
return presentingViewController.firstApplicableViewController()
} else if self.presentedViewController != nil {
let presentedViewController: UIViewController = self.presentedViewController!
if (presentedViewController is UIAlertController) {
return self
} else {
return presentedViewController.firstApplicableViewController()
}
} else {
return self
}
}

How to get top view controller when UIImagePickerController is presented?

At some time I present UIImagePickerViewController. Once it is presented, I call my function: UIStoryboard.topViewController():
extension UIStoryboard {
class func topViewController(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.rootViewController) -> UIViewController? {
if let nav = base as? UINavigationController {
return topViewController(nav.visibleViewController)
}
if let svc = base as? UISplitViewController where svc.viewControllers.count == 1 {
return topViewController(svc.viewControllers[0])
}
if let presented = base?.presentedViewController {
return topViewController(presented)
}
return base
}
}
When I print result, all I get is:
0x000000014cb2aa00
{
UIKit.UIResponder = {...}
}
How to get UIImagePickerController from topViewController() function?
Not a complete answer to your case, but here's how I find the top VC in my program. You should be able to edit it for your case.
class UIHelper {
static func getCurrentViewController() -> UIViewController? {
var currentViewController: UIViewController?
if let window = UIApplication.sharedApplication().delegate?.window {
currentViewController = window!.rootViewController?.presentedViewController
}
if currentViewController == nil {
return nil
}
//Check for my version of my main tab bar VC
if let tabBarController = currentViewController as? RootTabBarController {
currentViewController = tabBarController.selectedViewController
print("Tab bar presents \(currentViewController)")
}
// Check if it's a nav VC
if let navController = currentViewController as? UINavigationController {
currentViewController = navController.viewControllers[0] as? UIViewController
print("Nav controller presents \(currentViewController)")
}
print("Current controller: \(currentViewController)")
return currentViewController
}
}

Resources