从UIView/NSObject类显示UIAlertController

27

我有一个iOS应用程序正在运行, 为了支持iOS8,我正在将UIAlertView/UIActionSheet替换为UIAlertController

问题:
为了显示UIAlertController,我需要UIViewController类的presentViewController方法。
但是,UIAlertView是从UIView或NSObject继承的类中显示的
由于显然的原因,我无法获取[self presentViewController...]方法。

我的工作:
我尝试从当前窗口获取根视图控制器,并显示UIAlertController。

[[[UIApplication sharedApplication] keyWindow].rootViewController presentViewController ...]

但是我有一些旋转问题,比如如果我的当前视图控制器不支持旋转,则在打开UIAlertController时它将旋转。

问题:
是否有人遇到过同样的问题并找到了安全的解决方案?
如果是,请提供一些示例或给出一些指南。


我曾经遇到过这个问题。请查看我在stackoverflow的回答中获取最顶层的视图控制器的代码,以便呈现另一个视图控制器。我同意,在大多数情况下,从非视图控制器对象呈现视图控制器是不好的实践,但有时确实需要这样做。 - nvrtd frst
10个回答

45

今天我解决了一个本质上类似的问题。与Jageen一样,我遇到了一个情况,我想从非UIViewController类中呈现UIAlertController。在我的情况下,我希望在HTTP请求的失败块运行时弹出警报。

这就是我使用的方式,与我们的朋友不同,它对我来说工作得非常完美。

UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(errorAlert, animated: true, completion: nil)

谢谢。不过你认为Rikkles上面说的那个,你的范式真的有问题吗? - LevinsonTechnologies
1
rootViewController 已经展示其他内容时,这种方法将无法工作。 - Evgeny Aleksandrov
这对我来说是一个无操作。 - Bill

15

UIView类的更好解决方案如下:

ObjectiveC

UIViewController *currentTopVC = [self currentTopViewController];
currentTopVC.presentViewController......... 

- (UIViewController *)currentTopViewController
{
    UIViewController *topVC = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
    while (topVC.presentedViewController)
    {
        topVC = topVC.presentedViewController;
    }
    return topVC;
}

Swift

var topVC = UIApplication.sharedApplication().keyWindow?.rootViewController
while((topVC!.presentedViewController) != nil){
     topVC = topVC!.presentedViewController
}
topVC?.presentViewController........

2
这很棒 - 但我发现我可以更简单地实现它:在我的AlertViewController中,我只需添加以下代码:`UIViewController *topVC = [[[[UIApplication sharedApplication] delegate] window] rootViewController]; [topVC presentViewController:presentUpdatedDataAvailableAlert animated:YES completion:nil];` - rob123

13

我的解决方案如下:

Swift

class alert {
    func msg(message: String, title: String = "")
    {
        let alertView = UIAlertController(title: title, message: message, preferredStyle: .Alert)

        alertView.addAction(UIAlertAction(title: "Done", style: .Default, handler: nil))

        UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertView, animated: true, completion: nil)
    }
}

这里是使用示例:

let Alert = alert()
Alert.msg("My alert (without title)")
Alert.msg("This is my alert", title: "Warning!")

10

看起来您当前正在视图对象中触发警报窗口(pre-iOS8)。这是一种非常糟糕的实践,因为通常应该从操作和逻辑中触发警报。而且这个代码应该放在控制器中。

我建议您重构当前的代码,将触发警报的逻辑移动到正确的控制器中,然后您可以轻松地升级到iOS 8,只需使用 self 作为控制器即可。

如果您是从外部对象调用警报,则将控制器传递给调用警报的方法。在上游某个地方,您必须了解控制器。


+1 个建议,你是对的,调用 UIAlertView 的位置是错误的,但是在当前状态下我无法重构它,我在许多地方发现了同样的问题,等待更少修改的建议,无论如何感谢,如果没有其他建议,将应用你的逻辑。 - Jageen
2
以前,UIAlertView 可以从任何 UIViewNSObject 中工作。例如:从 UITableViewCell 显示警报是可以的。不需要从 UIViewController 实例中显示它。现在,UIAlertController 只能从 UIViewController 中呈现,这使得从单元格显示警报变得不容易。 - Dinesh Raja
通常警报应该从操作和逻辑中触发。而且那些代码应该存在于控制器中。 我更喜欢将业务逻辑放在模型中,远离(视图)控制器。控制器只应处理它们所控制的视图的可视化方面。所以这一切都没有意义。 - Jonny
@Jonny 业务逻辑存在于业务类中,但显示逻辑存在于显示控制器中。当显示控制器调用返回错误的业务逻辑时,它需要确定如何向用户显示错误信息。希望这样更清楚明白了。 - Rikkles

6

针对 Swift 4

UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true, completion: nil)

对于Swift 5


UIApplication.shared.windows.last?.rootViewController?.present(alert, animated: true)

4

我曾经遇到这样一个情况:有一个子视图包含一个用于关闭它的按钮。当我弹出一个确认框来确认该操作时,它会发送一条消息给代理 - 即包含该子视图的视图控制器 - 以便移除该子视图。

最初,我从一个UIView中弹出了一个UIAlertView。为了使用UIAlertController进行重构,因为UIAlertController无法像UIAlertView那样自己呈现,我想出了以下解决方案(在Swift中,很容易转换成ObjC):

向子视图添加一个协议:

protocol MySubviewDelegate {

    // called when user taps subview/delete button
    //   or, you could call it from a gesture handler, etc.
    func displayAlert(alert : UIAlertController)

    // called when user confirms delete from the alert controller
    func shouldRemoveSubview(sender : AnyObject)

}

为子视图添加代理,并添加按钮/手势点击的处理程序:

class MySubview : UIView {

    var subviewDelegate : MySubviewDelegate!

    ...

    func handleTap(sender : AnyObject) {

        // set up the alert controller here
        var alert = UIAlertController(title: "Confirm Delete", 
            message: "This action is permanent. Do you wish to continue?", 
            preferredStyle: UIAlertControllerStyle.Alert)

        // Cancel action 
        //   nil handler means "no action if Cancel button selected"
        alert.addAction(UIAlertAction(title: "Cancel",
            style: UIAlertActionStyle.Cancel,
            handler: nil))

        // Confirm action
        alert.addAction(UIAlertAction(title: "Confirm",
            style: UIAlertActionStyle.Default,
            handler: { (action : UIAlertAction!) -> Void in

                // call delegate method to perform confirmed action, - i.e. remove
                self.subviewDelegate.shouldRemoveSubview(self)
        }))

        // call delegate method to display alert controller
        //   send alert object to delegate
        self.subviewDelegate.displayAlert(alert)
    }
}

在调用的UIViewController中将其设置为子视图的代理,例如在其viewDidLoad()方法中,并包含协议方法:

class viewController : UIViewController, MySubviewDelegate {

    override func viewDidLoad() {

        super.viewDidLoad()

        self.subviewDelegate = self

        ...
    }

    func displayAlert(alert : UIAlertController) {

        presentViewController(alert, animated: true, completion: nil)
    }

    func shouldRemoveSubview(sender : AnyObject) {

        // cast as UIView / MySubview subclass
        var subview = sender as MySubview

       // remove the subview / perform the desired action
       subview.removeFromSuperview()

       ...
    }

  ...
}

这样可以避免查找最上层的视图控制器,或向子视图传递视图控制器的引用(除了在对象/委托关系中)。

3

在Swift 3中:

UIApplication.shared.keyWindow?.rootViewController?.present(alertView, animated: true, completion: nil)

0

在NSObject类中显示UIAlertController,请使用以下代码。

    UIAlertController * popup =   [UIAlertController
                              alertControllerWithTitle:nil
                              message:nil
                              preferredStyle:UIAlertControllerStyleActionSheet];

    UIAlertAction* cancel = [UIAlertAction
                             actionWithTitle:@"Cancel"
                             style:UIAlertActionStyleCancel
                             handler:^(UIAlertAction * action) {
                                 [popup dismissViewControllerAnimated:YES completion:nil];
                             }];
    [popup addAction:cancel];

    UIViewController *rootViewController = [[Helper shareInstance] topViewController];
    [rootViewController presentViewController:popup animated:YES completion:nil];

// 将以下方法放入您的全局助手类中。

- (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];
}

0

通常,警报应该在视图控制器中处理。以下是所需代码的示例:

Swift 3

private func displayError(message: String) {
    let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert)
    let okayAction = UIAlertAction(title: "Okay", style: .default, handler: nil)
    alertController.addAction(okayAction)
    present(alertController, animated: true, completion: nil)
}

0

我知道这个问题已经有答案了... 但是因为我也在寻找同样的问题,但以上解决方案都不适用于我。

所以经过多次尝试和错误,最终我找到了一个非常简单和可持续的解决方案。

    func showError(title: String?, error: String?) {

    DispatchQueue.main.async(execute: {

        let alert = UIAlertController(title: title, message: error, preferredStyle: UIAlertControllerStyle.alert)

        alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))

        CommonMethods.instance.topMostController()?.present(alert, animated: true, completion: nil)

    })
}

static let instance = CommonMethods()

fileprivate func topMostController() -> UIViewController? {

    var presentedVC = UIApplication.shared.keyWindow?.rootViewController
    while let pVC = presentedVC?.presentedViewController {
        presentedVC = pVC
    }

    if presentedVC == nil {  }
    return presentedVC
}

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