How to present UIAlertController when not in a view controller?

前端 未结 30 3117
庸人自扰
庸人自扰 2020-11-22 06:21

Scenario: The user taps on a button on a view controller. The view controller is the topmost (obviously) in the navigation stack. The tap invokes a utility class method call

相关标签:
30条回答
  • 2020-11-22 07:09

    Updated to work with iOS 13 Scenes which breaks the new UIWindow approach. Swift 5.1.

    fileprivate var alertWindows = [UIAlertController:UIWindow]()
    
    extension UIAlertController {
    
        func presentInNewWindow(animated: Bool, completion: (() -> Void)?) {
            let foregroundActiveScene = UIApplication.shared.connectedScenes.filter { $0.activationState == .foregroundActive }.first
            guard let foregroundWindowScene = foregroundActiveScene as? UIWindowScene else { return }
    
            let window = UIWindow(windowScene: foregroundWindowScene)
            alertWindows[self] = window
    
            window.rootViewController = UIViewController()
            window.windowLevel = .alert + 1
            window.makeKeyAndVisible()
            window.rootViewController!.present( self, animated: animated, completion: completion)
        }
    
        open override func viewDidDisappear(_ animated: Bool) {
            super.viewDidDisappear(animated)
            alertWindows[self] = nil
        }
    
    }
    
    0 讨论(0)
  • 2020-11-22 07:11

    Swift

    let alertController = UIAlertController(title: "title", message: "message", preferredStyle: .alert)
    //...
    var rootViewController = UIApplication.shared.keyWindow?.rootViewController
    if let navigationController = rootViewController as? UINavigationController {
        rootViewController = navigationController.viewControllers.first
    }
    if let tabBarController = rootViewController as? UITabBarController {
        rootViewController = tabBarController.selectedViewController
    }
    //...
    rootViewController?.present(alertController, animated: true, completion: nil)
    

    Objective-C

    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Title" message:@"message" preferredStyle:UIAlertControllerStyleAlert];
    //...
    id rootViewController = [UIApplication sharedApplication].delegate.window.rootViewController;
    if([rootViewController isKindOfClass:[UINavigationController class]])
    {
        rootViewController = ((UINavigationController *)rootViewController).viewControllers.firstObject;
    }
    if([rootViewController isKindOfClass:[UITabBarController class]])
    {
        rootViewController = ((UITabBarController *)rootViewController).selectedViewController;
    }
    //...
    [rootViewController presentViewController:alertController animated:YES completion:nil];
    
    0 讨论(0)
  • 2020-11-22 07:11

    Shorthand way to do present the alert in Objective-C:

    [[[[UIApplication sharedApplication] keyWindow] rootViewController] presentViewController:alertController animated:YES completion:nil];
    

    Where alertController is your UIAlertController object.

    NOTE: You'll also need to make sure your helper class extends UIViewController

    0 讨论(0)
  • 2020-11-22 07:13

    Here's mythicalcoder's answer as an extension, tested & working in Swift 4:

    extension UIAlertController {
    
        func presentInOwnWindow(animated: Bool, completion: (() -> Void)?) {
            let alertWindow = UIWindow(frame: UIScreen.main.bounds)
            alertWindow.rootViewController = UIViewController()
            alertWindow.windowLevel = UIWindowLevelAlert + 1;
            alertWindow.makeKeyAndVisible()
            alertWindow.rootViewController?.present(self, animated: animated, completion: completion)
        }
    
    }
    

    Example usage:

    let alertController = UIAlertController(title: "<Alert Title>", message: "<Alert Message>", preferredStyle: .alert)
    alertController.addAction(UIAlertAction(title: "Close", style: .cancel, handler: nil))
    alertController.presentInOwnWindow(animated: true, completion: {
        print("completed")
    })
    
    0 讨论(0)
  • 2020-11-22 07:16

    Swift 4+

    Solution I use for years with no issues at all. First of all I extend UIWindow to find it's visibleViewController. NOTE: if you using custom collection* classes (such as side menu) you should add handler for this case in following extension. After getting top most view controller it's easy to present UIAlertController just like UIAlertView.

    extension UIAlertController {
    
      func show(animated: Bool = true, completion: (() -> Void)? = nil) {
        if let visibleViewController = UIApplication.shared.keyWindow?.visibleViewController {
          visibleViewController.present(self, animated: animated, completion: completion)
        }
      }
    
    }
    
    extension UIWindow {
    
      var visibleViewController: UIViewController? {
        guard let rootViewController = rootViewController else {
          return nil
        }
        return visibleViewController(for: rootViewController)
      }
    
      private func visibleViewController(for controller: UIViewController) -> UIViewController {
        var nextOnStackViewController: UIViewController? = nil
        if let presented = controller.presentedViewController {
          nextOnStackViewController = presented
        } else if let navigationController = controller as? UINavigationController,
          let visible = navigationController.visibleViewController {
          nextOnStackViewController = visible
        } else if let tabBarController = controller as? UITabBarController,
          let visible = (tabBarController.selectedViewController ??
            tabBarController.presentedViewController) {
          nextOnStackViewController = visible
        }
    
        if let nextOnStackViewController = nextOnStackViewController {
          return visibleViewController(for: nextOnStackViewController)
        } else {
          return controller
        }
      }
    
    }
    
    0 讨论(0)
  • 2020-11-22 07:16

    Seems to work:

    static UIViewController *viewControllerForView(UIView *view) {
        UIResponder *responder = view;
        do {
            responder = [responder nextResponder];
        }
        while (responder && ![responder isKindOfClass:[UIViewController class]]);
        return (UIViewController *)responder;
    }
    
    -(void)showActionSheet {
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:nil message:nil preferredStyle:UIAlertControllerStyleActionSheet];
        [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
        [alertController addAction:[UIAlertAction actionWithTitle:@"Do it" style:UIAlertActionStyleDefault handler:nil]];
        [viewControllerForView(self) presentViewController:alertController animated:YES completion:nil];
    }
    
    0 讨论(0)
提交回复
热议问题