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.
Related
I'm trying to handle push notification click. When push notification comes in, I want to direct the user to a certain part of the application after clicking on push notification. Firstly, there is SplashScreen in the application, where it shows a TabBar as "Present Modally" by checking whether the user has previously logged in from API Call. What I want to show the user is the content of a feed in the table view in the navigation controller in the first tab of the TabBar. But I can't reach it and show it as clicked on tableview.
-SplashScreen -------> TabBar -> Navigation Controller -> Table View Controller -> ContentView(I want to show)
I tried writing the UIApplication extension but it didn't work.
Here's the extension
extension UIApplication {
class func topViewController(base: UIViewController? = (UIApplication.shared.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)
}
return base
}
}
I wrote the following code in didFinishLaunchingWithOptions
if let option = launchOptions {
let info = option[UIApplication.LaunchOptionsKey.remoteNotification] as? [String:Any]
if let info = info {
if info["type"] as! String == "site-notification" {
let contentVC = Destination().FeedCoontentVC
contentVC.selectedSite = (info["link"] as! String)
contentVC.title = "asd"
UIApplication.topViewController()?.present(contentVC, animated: true, completion: nil)
}
}
}
if info["type"] as! String == "site-notification" {
let tabbarSB = UIStoryboard(name: StoryBoard.tabBar, bundle: nil)
let tabbarVC = tabbarSB.instantiateViewController(withIdentifier: ViewController.TabBar) as! TabBarController
self.window?.rootViewController = tabbarVC
tabbarVC.viewControllers = [] // add your tab view controllers
for child in (tabbarVC.childViewControllers) {
if child.restorationIdentifier == "tablevc" //add restoration id to storyboard {
tabbarVC.selectedIndex = 1
let tableVC = (child.childViewControllers[0]) as! TableViewController
let contentSB = UIStoryboard(name: StoryBoard.contentSB , bundle: nil)
let contentVC = contentSB.instantiateViewController(withIdentifier: ViewController.contentVC ) as! FeedCoontentVC
contentVC.selectedSite = (info["link"] as! String)
contentVC.title = "asd"
tableVC.navigationController?.pushViewController(contentVC, animated: false)
}
}
}
When APP is Launching - start SigninView - it's Okey. Next if success - I need showTripController(). Function work but nothing show? What's a problem?
func showSigninView() {
let controller = self.window?.rootViewController!.storyboard?.instantiateViewControllerWithIdentifier("DRVAuthorizationViewController")
self.window?.rootViewController!.presentViewController(controller!, animated: true, completion: nil)
}
func showTripController() {
let cv = self.window?.rootViewController!.storyboard?.instantiateViewControllerWithIdentifier("DRVTripTableViewController")
let nc = UINavigationController()
self.window?.rootViewController!.presentViewController(nc, animated:true, completion: nil)
nc.pushViewController(cv!, animated: true);
}
First of all you must add this before you use window :
self.window.makeKeyAndVisible()
Another thing to keep in mind is:
Sometimes keyWindow may have been replaced by window with nil rootViewController (showing UIAlertViews, UIActionSheets on iPhone, etc), in that case you should use UIView's window property.
So, instead of using rootViewController, use the top one presented by it:
extension UIApplication {
class func topViewController(base: UIViewController? = UIApplication.sharedApplication().keyWindow?.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)
}
return base
}
}
if let topController = UIApplication.topViewController() {
topController.presentViewController(vc, animated: true, completion: nil)
}
Replace last 3 lines of showTripController as below:
let nc = UINavigationController(rootViewController: cv));
self.window!.rootViewController = nc
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
}
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
}
}
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
}
}