Anton
Anton

Reputation: 155

Calling alert view from another class, but one is not in hierarchy

I'm trying to call function from one class in view controller:

var rate = RateMyApp.sharedInstance 
rate.showRatingAlert()

and in RateMyApp class I call alert view:

 var window: AnyObject = UIApplication.sharedApplication().keyWindow!;
        var vc = window.rootViewController;
      vc?!.presentViewController(alert, animated: true, completion: nil)

However, the alert is not shown and there is a kind of warning:

 Warning: Attempt to present <UIAlertController: 0x15fe3dcf0> on <drawtext.ViewController: 0x16002a800> whose view is not in the window hierarchy!

That happens from time to time, if one hides the app and then opens it again several times, the alert sometimes is shown.

What can cause such strange behavior?

Upvotes: 0

Views: 782

Answers (4)

Raja Vikram
Raja Vikram

Reputation: 1980

While using UIAlertController it should be presented form the topmost ViewController in the view hierarchy.

so i suggest:

var rate = RateMyApp.sharedInstance 
rate.showRatingAlert(self)

In RateMyApp class:

func showRatingAlert(sender:UIViewController){
//..... your UIAlertController here
 sender.presentViewController(alert, animated: true, completion: nil)
}

On a more general note, the func presentViewController(:_) should be called only from the top-most View Controller.

You can create an extension for it like so:

extension UIViewController {
  @warn_unused_result
  static func topViewController(base: UIViewController? = UIApplication.sharedApplication().windows.first?.rootViewController) -> UIViewController? {

    if let nav = base as? UINavigationController {
        return topViewController(nav.visibleViewController)
    }

    if let tab = base as? UITabBarController {
        let moreNavigationController = tab.moreNavigationController

        if let top = moreNavigationController.topViewController where top.view.window != nil {
            return topViewController(top)
        } else if let selected = tab.selectedViewController {
            return topViewController(selected)
        }
    }

    if let presented = base?.presentedViewController {
        return topViewController(presented)
    }

    if let splitVC = base as? UISplitViewController {
        if let lastVC = splitVC.viewControllers.last {
            return topViewController(lastVC)
        }
        else {
            return splitVC
        }
    }

    if let lastChildVC = base?.childViewControllers.last {
        return topViewController(lastChildVC)
    }

    return base
   }
}

You can call the alert as follows:

func showRatingAlert() {
   if let vc = UIViewController.topViewController() {
      vc.presentViewController(alert, animated: true, completion: nil)
   }
}

Upvotes: 1

Anton
Anton

Reputation: 155

Seems to be solved:

     var window = UIApplication.sharedApplication().keyWindow!
           // var vc = window.rootViewController!!.presentingViewController

        var vc = window.rootViewController;
        while (vc!.presentedViewController != nil)
        {
            vc = vc!.presentedViewController;
        }

      vc?.presentViewController(alert, animated: true, completion: nil)

looks like Chetan's solution

Upvotes: 1

Reming Hsu
Reming Hsu

Reputation: 2225

Do it when the window is up

dispatch_async(dispatch_get_main_queue(), {
    var window: AnyObject = UIApplication.sharedApplication().keyWindow!;
    var vc = window.rootViewController;
    vc?!.presentViewController(alert, animated: true, completion: nil)
})

Upvotes: 0

Chetan Prajapati
Chetan Prajapati

Reputation: 2297

As per my thought you just need

  1. Get top most viewcontroller [currently presented]

  2. Present your alert.

To Get Topmost View Controller

[This is obj C code. Please make efforts to convert in swift]

-(UIViewController*)topViewController {
    return [self topViewControllerWithRootViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}

-(UIViewController*)topViewControllerWithRootViewController:(UIViewController*)rootViewController {
    if ([rootViewController isKindOfClass:[UITabBarController class]]) {
        UITabBarController* tabBarController = (UITabBarController*)rootViewController;
        return [self topViewControllerWithRootViewController:tabBarController.selectedViewController];
    } else if ([rootViewController isKindOfClass:[UINavigationController class]]) {
        UINavigationController* navigationController = (UINavigationController*)rootViewController;
        return [self topViewControllerWithRootViewController:navigationController.visibleViewController];
    } else if (rootViewController.presentedViewController) {
        UIViewController* presentedViewController = rootViewController.presentedViewController;
        return [self topViewControllerWithRootViewController:presentedViewController];
    } else {
        return rootViewController;
    }
}

Then

UIViewController *topController = [self topViewController];

Now present your alert on topController.

Sorry i can't deliver you Swift Code. But Hope this is enough to hint.

Upvotes: 0

Related Questions