Search code examples
iosuiviewcontrollerios8presentviewcontroller

dismissViewControllerAnimated:completion: on iOS 8


In iOS <= 7, directly after calling dismissViewControllerAnimated:completion: would result in presentedViewController being nil. In iOS 8, presentedViewController still points to the presented viewcontroller, right until the completion block is executed.

[self dismissViewControllerAnimated:NO completion:^{
    //self.presentedViewController is nil
}];
//self.presentedViewController is nil on iOS 7, but not nil on iOS 8

So in iOS 8 we cannot rely on the property presentedViewController in order to find out which viewcontroller is currently the top visible viewcontroller.

In iOS 8, alerts need to be presented onto a viewcontroller (which poses another problem). They will not show if the viewcontroller we try to present on already presents a viewcontroller.

If I just dismissed my presented viewcontroller and show a UIAlertController on the currently top visible viewcontroller (by recursively searching for the last presentedViewController), then it will of course not show but log an error message: "Warning: Attempt to present on whose view is not in the window hierarchy!"

  1. Is this a bug in iOS 8 or just the new way?
  2. How can I find out the viewcontroller I can present my UIALertController on?

Solution

  • I found a workaround to find out which viewcontroller I can present the alert upon:

    @implementation UIViewController (visibleViewController)
    
    - (UIViewController *)my_visibleViewController {
    
        if ([self isKindOfClass:[UINavigationController class]]) {
            // do not use method visibleViewController as the presentedViewController could beingDismissed
            return [[(UINavigationController *)self topViewController] my_visibleViewController];
        }
    
        if ([self isKindOfClass:[UITabBarController class]]) {
            return [[(UITabBarController *)self selectedViewController] my_visibleViewController];
        }
    
        if (self.presentedViewController == nil || self.presentedViewController.isBeingDismissed) {
            return self;
        }
    
        return [self.presentedViewController my_visibleViewController];
    }
    
    @end
    
    // To show a UIAlertController, present on the following viewcontroller:
    UIViewController *visibleViewController = [[UIApplication sharedApplication].delegate.window.rootViewController my_visibleViewController];
    

    Swift 3:

    import UIKit
    
    extension UIViewController {
        func visibleViewController() -> UIViewController? {
            guard !(self is UINavigationController) else {
                let navVC = self as! UINavigationController
                return navVC.topViewController?.visibleViewController()
            }
    
            guard !(self is UITabBarController) else {
                let tabVC = self as! UITabBarController
                return tabVC.selectedViewController?.visibleViewController()
            }
    
            if self.presentedViewController == nil || 
               self.presentedViewController!.isBeingDismissed {
                return self
            }
    
            return self.presentedViewController?.visibleViewController()
        }
    }