AlertController不在窗口层次结构中。

50

我刚刚创建了一个带有ViewController类的单视图应用程序项目。我想从我的自定义类中的一个函数中显示一个UIAlertController。

这是我的带有警报的类。

class AlertController: UIViewController {
     func showAlert() { 
         var alert = UIAlertController(title: "abc", message: "def", preferredStyle: .Alert)
         self.presentViewController(alert, animated: true, completion: nil)
     }
}

这里是执行警报的ViewController。

class ViewController: UIViewController {
   override func viewDidLoad() {
       super.viewDidLoad()  
   }

   @IBAction func showAlertButton(sender: AnyObject) {
       var alert = AlertController()
       alert.showAlert()
   }
}

我得到的不是一个漂亮的警告框,而是这个:

警告:尝试在Sprint1.AlertController: 0x797cc500中呈现UIAlertController: 0x797d2d20,但其视图不在窗口层次结构中!

我该怎么办?


你为什么不使用self.show()呢? - Alex
2
使用 self.show() 替换 self.presentViewController(alert, animated: true, completion: nil)。 - Alex
"AlertController" 没有名为 'show' 的成员。 - wtznc
如果您希望它的行为像普通的UIAlertview一样,您可以将其子类化为UIAlertview而不是UIviewcontroller。这是您要寻找的吗? - Alex
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/73777/discussion-between-wtznc-and-alex。 - wtznc
显示剩余2条评论
10个回答

64
如果您从模态控制器实例化UIAlertController,则需要在viewDidAppear中执行此操作,而不是在viewDidLoad中执行,否则会出现错误。

这是我的代码(Swift 4):

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    let alertController = UIAlertController(title: "Foo", message: "Bar", preferredStyle: .alert)

    alertController.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
    present(alertController, animated: true, completion: nil)
}

1
谢谢,你的解决方案有效了...但我想知道为什么它在viewDidLoad中不起作用... - ioopl
@ioopl 我猜这是为了防止在视图控制器出现之前或者在动画过程中显示警报。 - Skoua

18

让我们来看看您的视图层次结构。您有一个ViewController。然后您创建了一个AlertController,您没有将其添加到您的层次结构中,并在其上调用了一个实例方法,该方法试图使用AlertController作为呈现控制器来显示另一个控制器(UIAlertController)。

+ ViewController
    + AlertController (not in hierarchy)
        + UIAlertController (cannot be presented from AlertController)
为了简化您的代码。
class ViewController: UIViewController {
   override func viewDidLoad() {
       super.viewDidLoad()  
   }

   @IBAction func showAlertButton(sender: AnyObject) {
       var alert = UIAlertController(title: "abc", message: "def", preferredStyle: .Alert)
       self.presentViewController(alert, animated: true, completion: nil)
   }
}

这会起作用。

如果您需要AlertController,则首先需要将其添加到层次结构中,例如使用addChildViewController或使用另一个presentViewController调用。

如果您希望该类只是创建警报的辅助程序,则应如下所示:

class AlertHelper {
    func showAlert(fromController controller: UIViewController) { 
        var alert = UIAlertController(title: "abc", message: "def", preferredStyle: .Alert)
        controller.presentViewController(alert, animated: true, completion: nil)
    }
}

被称为

 var alert = AlertHelper()
 alert.showAlert(fromController: self)

我需要 AlertController,因为我必须在这个类中执行更多的操作。我尝试过 self.addChildViewController(alert)self.presentViewController(alert, animated: true, completion: nil),但是当按钮被点击时程序会崩溃。原因是:“应用程序试图以模态方式呈现一个活动控制器 <Sprint1.AlertController: 0x78f80d60>。” - wtznc
@wtznc 你需要在那里做什么?显然,你的问题是一个架构问题。 - Sulthan
我必须根据后端的答复延迟显示警报。 - wtznc

14

您可以使用以下函数从任何位置调用警报,只需将这些方法包含在AnyClass中即可。

class func topMostController() -> UIViewController {
        var topController: UIViewController? = UIApplication.shared.keyWindow?.rootViewController
        while ((topController?.presentedViewController) != nil) {
            topController = topController?.presentedViewController
        }
        return topController!
    }

    class func alert(message:String){
        let alert=UIAlertController(title: "AppName", message: message, preferredStyle: .alert);
        let cancelAction: UIAlertAction = UIAlertAction(title: "OK", style: .cancel) { action -> Void in

        }
        alert.addAction(cancelAction)
        AnyClass.topMostController().present(alert, animated: true, completion: nil);
    }

然后打电话

AnyClass.alert(message:"Your Message")

5

只需编写以下3行代码即可完成。

Swift 3.0

private func presentViewController(alert: UIAlertController, animated flag: Bool, completion: (() -> Void)?) -> Void {
     UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: flag, completion: completion)
  }

Swift 2.0

  private func presentViewController(alert: UIAlertController, animated flag: Bool, completion: (() -> Void)?) -> Void {
     UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alert, animated: flag, completion: completion)
  }

这对我来说完全有效。我将其实现在我的...Utilities类中,嘿哎呦! - user462990
5
即使这样,仍然会出现一些情况下rootViewController的视图被释放(例如在其前面呈现modal vc时),你的警报将无法显示。您将收到错误警告:“尝试在不在窗口层次结构中的<root视图控制器>上呈现<alert>!” - Dorian Roy

2
    let alert = UIAlertController(title: "", message: "YOU SUCCESSFULLY\nCREATED A NEW\nALERT CONTROLLER", preferredStyle: .alert)
    func okAlert(alert: UIAlertAction!)
    {
    
    }
    alert.addAction(UIAlertAction(title: "OK", style: .default, handler: okAlert))
    
    let scenes = UIApplication.shared.connectedScenes
    let windowScene = scenes.first as? UIWindowScene
    let window = windowScene?.windows.first
    var rootVC = window?.rootViewController
    
    if var topController = rootVC
    {
        while let presentedViewController = topController.presentedViewController
        {
            topController = presentedViewController
        }
        rootVC = topController
    }
    rootVC?.present(alert, animated: true, completion: nil)

你救了我!我一直在遇到获取呈现视图控制器的问题!谢谢!!! - Julian Silvestri

2
如果您想创建一个用于显示警报的单独类,应该将其子类化为NSObject而不是UIViewController。
并且从它被初始化的ViewController传递引用到showAlert函数中,这样您就可以在那里呈现警报视图。

2

这是一个在 Swift3 中的 Utility.swift 类(不是 UIViewController)中的 UIAlertController 代码,感谢 Mitsuaki!

private func presentViewController(alert: UIAlertController, animated flag: Bool, completion: (() -> Void)?) -> Void {
    UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: flag, completion: completion)
}
func warningAlert(title: String, message: String ){
    let alert = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
    alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler:  { (action) -> Void in
    }))        
 //   self.present(alert, animated: true, completion: nil)
    presentViewController(alert: alert, animated: true, completion: nil)
}

4
即使如此,仍然存在一些情况,其中rootViewController的视图已被释放(例如在其前面呈现模态vc时),您的警报将无法显示。你会收到错误信息:“Warning: Attempt to present <alert> on <root view controller> whose view is not in the window hierarchy!” - Dorian Roy

0

在viewDidLoad方法和触发警报方法之间加入轻微的延迟对我很有帮助:

   [self performSelector:@selector(checkPhotoPermission) withObject:nil afterDelay:0.1f];

0
这对我有用:
- (UIViewController *)topViewController{
  return [self topViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
}

- (UIViewController *)topViewController:(UIViewController *)rootViewController
{
  if (rootViewController.presentedViewController == nil) {
    return rootViewController;
  }

  if ([rootViewController.presentedViewController isMemberOfClass:[UINavigationController class]]) {
    UINavigationController *navigationController = (UINavigationController *)rootViewController.presentedViewController;
    UIViewController *lastViewController = [[navigationController viewControllers] lastObject];
    return [self topViewController:lastViewController];
  }

  UIViewController *presentedViewController = (UIViewController *)rootViewController.presentedViewController;
  return [self topViewController:presentedViewController];
}

实现:

UIViewController * topViewController = [self topViewController];

使用alert:

[topViewController presentViewController:yourAlert animated:YES completion:nil];

您可以从应用程序中的任何类(使用UIKit:#import <UIKit/UIKit.h>)发送警报。

源在此处。


0
// I always find it helpful when you want to alert from anywhere it's codebase 
// if you find the error above mentioned in the question' title.

let controller = UIAlertController(title: "", message: "Alert!", preferredStyle: UIAlertController.Style.alert)
    
let action = UIAlertAction(title: "Cancel"   , style: UIAlertAction.Style.cancel, handler: nil)
controller.addAction(action)

// Find Root View Controller
var rootVC = UIApplication.shared.windows.first?.rootViewController

if var topController = rootVC {
    while let presentedViewController = topController.presentedViewController {
        topController = presentedViewController
    }
    rootVC = topController
}
rootVC?.present(controller, animated: true, completion: nil)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接