如何在点击UIAlertController外部时关闭UIAlertController?

65

如何在点击 UIAlertController 以外的区域时关闭它?

我可以添加一个样式为UIAlertActionStyleCancelUIAlertAction来关闭UIAlertController

但是,我想要添加一个功能,当用户点击 UIAlertController 以外的区域时,UIAlertController 也能够关闭。怎么做呢?谢谢。


2
嗨Leizh00701,看看这个: https://dev59.com/yV8e5IYBdhLWcg3wssEV - Harish
发送者是什么:UIControl *aControl =(UIControl *)sender; - leizh00701
1
UIControl是控制对象的基类,例如按钮和滑块,它们传达用户意图给应用程序。请查看以下链接:https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIControl_Class/index.html - Harish
11个回答

55

添加一个带有样式 UIAlertActionStyleCancel 的独立取消操作,以便当用户点击外部时,可以获得回调。

Obj-c

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert Title" message:@"A Message" preferredStyle:UIAlertControllerStyleActionSheet];
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
 // Called when user taps outside
}]];

Swift 5.0

let alertController = UIAlertController(title: "Alert Title", message: "A Message", preferredStyle: .actionSheet)             
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { 
    action in
         // Called when user taps outside
}))

这不是警告对话框的标准行为,因为它们被设计为始终是模态的。 - Kudit
在iPhone上,UIAlertActionStyleCancel被显示出来(并且点击无效),而在iPad上则被隐藏,因为它被替换为一个覆盖视图的点击。 - 93sauu
20
您的 Swift 5.0 版本只是添加了一个“取消”按钮,您可以为其分配代码,但在警告框之外轻敲不会触发任何操作,您必须单击按钮才能取消。 - Neph
和我一样。上面的实现没有使对话框在触摸其外部时消失。 - Bréndal Teixeira

54

如果你的目标设备运行iOS > 9.3,并使用Swift,同时preferredStyle为Alert,你可以使用以下代码片段:


let alert = UIAlertController(title: "Title", message: "Message", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
present(alert, animated: true, completion: nil)
func showAlertBtnClicked(sender: UIButton) {
    let alert = UIAlertController(title: "This is title", message: "This is message", preferredStyle: .Alert)
    self.presentViewController(alert, animated: true, completion:{
        alert.view.superview?.userInteractionEnabled = true
        alert.view.superview?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.alertControllerBackgroundTapped)))
    })
}

func alertControllerBackgroundTapped()
{
    self.dismissViewControllerAnimated(true, completion: nil)
}

使用Swift 3:

func showAlertBtnClicked(sender: UIButton) {
    let alert = UIAlertController(title: "This is title", message: "This is message", preferredStyle: .alert)
    self.present(alert, animated: true) {
        alert.view.superview?.isUserInteractionEnabled = true
        alert.view.superview?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.alertControllerBackgroundTapped)))
    }
}

func alertControllerBackgroundTapped()
{
    self.dismiss(animated: true, completion: nil)
}

15
从iOS 9.3开始,您需要将手势识别器添加到超级视图(alert.view.superview.subviews[1])的第二个子视图上,而不是超级视图本身。正如文档中所述,UIAlertController的视图层次结构是私有的,因此不能保证它在未来的iOS发布中不会改变。 - Thanh Pham
1
我将你的答案与sfongxing10000的混合在一起,然后想到了将轻拍手势添加到alert.view.superview及其所有subviews中。目前它可以工作,适用于iOS 11.2! - FonzTech
2
不确定这是否是Swift 5的更改,但您必须向alertControllerBackgroundTapped函数添加@objc,否则Xcode会解释。除此之外,Swift 3版本也适用于Swift 5,谢谢。 - Neph

41

Swift, Xcode 9

使用取消按钮关闭 AlertController

为你的 alertController 提供一个 action,其中 UIAlertAction 的样式是 .cancel

let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)

使用此方法,当用户点击取消操作按钮或 alertController 外部时,alertController 将被关闭。

如果您不希望用户在触摸 alertController 之外后关闭 alertController,请在 present 方法的完成闭包中禁用 alertController 的第一个子视图的用户交互。

self.present(alertController, animated: true) {
     alertController.view.superview?.subviews[0].isUserInteractionEnabled = false
    }

点击控制器视图外部时关闭AlertController

如果您不想在控制器视图中显示取消按钮,并希望在用户在控制器视图外部触摸时关闭控制器,请这样做。

self.present(alertController, animated: true) {
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.dismissAlertController))
        alertController.view.superview?.subviews[0].addGestureRecognizer(tapGesture)
}

@objc func dismissAlertController(){
    self.dismiss(animated: true, completion: nil)
}

使用此方法,当用户点击取消操作按钮以及警报控制器外部时,警报控制器将被解除。我不认为这是正确的,在单击警报控制器警报框外部时,它不会放弃第一响应者。只有在使用操作表时单击alertController警报框之外才会放弃第一响应者。 - stromyc

11

如果你正在使用 Swift :

使用addAction(_:)style:UIAlertActionStyle.Cancel 添加一个操作。

当你点击按钮或框架外部时,将调用 `handler`。

var alertVC = UIAlertController(...) // initialize your Alert View Controller

        alertVC.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: {
            (alertAction: UIAlertAction!) in
            alertVC.dismissViewControllerAnimated(true, completion: nil)
        }))

Objective-C :

目标-C:
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:...];


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

很不错,但是有没有可能不显示“关闭”按钮? - bashan
@bashan 是的,你必须选择正确的 UIAlertActionStyle。在这个例子中,我将按钮设置为 Cancel 样式。你可以将它设置为 Default - Kevin Machado
将按钮更改为“默认”将禁用在菜单外部轻触以关闭菜单的功能。 - bashan
24
有没有一种方法可以在菜单外部按下以关闭它,但不需要"取消"按钮? - bashan
@bashan 不,iOS SDK没有提供那个功能。 - user11638666

6

Swift 4:

当用户点击 UIAlertController 创建的 Action Sheet 外部时,关闭 Action Sheet。

代码片段:

// Declare Action Sheet reference
var actionSheet: UIAlertController!

// Init and Show Action Sheet
func showActionSheetClicked(sender: UIButton) {

    // Init Action Sheet
    actionSheet = UIAlertController(title: "Title", message: "Message", preferredStyle: .actionSheet)

    self.present(actionSheet, animated: true) {
        // Enabling Interaction for Transperent Full Screen Overlay
        self.actionSheet.view.superview?.subviews.first?.isUserInteractionEnabled = true

        // Adding Tap Gesture to Overlay
        self.actionSheet.view.superview?.subviews.first?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.actionSheetBackgroundTapped)))
    }
}

// To dismiss Action Sheet on Tap
@objc func actionSheetBackgroundTapped() {
    self.actionSheet.dismiss(animated: true, completion: nil)
}

1
是的,在完成时添加手势识别器可以使其工作。谢谢! - levan

2
- (void)addBackgroundDismissTapForAlert:(UIAlertController *)alert {
    if (!alert.view.superview) {
        return;
    }
    alert.view.superview.userInteractionEnabled = YES;
    [alert.view.superview addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(alertControllerBackgroundTapped)]];
    for (UIView *subV in alert.view.superview.subviews) {
        if (subV.width && subV.height) {
            subV.userInteractionEnabled = YES;
            [subV addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(alertControllerBackgroundTapped)]];
        }
    }
}
- (void)alertControllerBackgroundTapped {

    [self dismissViewControllerAnimated: YES
                         completion: nil];
}

这实际上是可行的,从iOS 11.2开始!我不得不将相同的轻拍手势添加到所有子视图中。 - FonzTech

2

在 Obj-C 中最简单的方法:

UIAlertController *alert = [UIAlertController alertControllerWithTitle: ...
[self presentViewController:alert animated:YES completion:^{
                                       [alert.view.superview addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(alertControllerBackgroundTapped)]];
                                   }];

然后:

- (void)alertControllerBackgroundTapped
{
    [self dismissViewControllerAnimated: YES
                             completion: nil];
}

1

如果您查看警告框的父视图进行调试,您会发现它并不像在_UIAlertControllerView的UITransitionView上添加一个轻拍手势识别器那么简单。相反,您可以这样做。

[presenter presentViewController:alertController animated:YES completion:^{
    NSArray <UIView *>* superviewSubviews = alertController.view.superview.subviews;
    for (UIView *subview in superviewSubviews) {
        if (CGRectEqualToRect(subview.bounds, weakSelf.view.bounds)) {
            [subview addSingleTapGestureWithTarget:weakSelf action:@selector(dismissModalTestViewController)];
        }
    }
}];

1
在iOS 15上,UIAlertController的视图层次结构似乎又一次发生了变化。它被呈现为一个包含控制器本身的新UIWindow。因此,为了在点击外部时关闭它:
present(alertController, animated: true) { [weak self] in
    guard let self = self else { return }

    let dismissGesture = UITapGestureRecognizer(target: self, action: #selector(self.shouldDismiss))

    self.alertController.view.window?.isUserInteractionEnabled = true
    self.alertController.view.window?.addGestureRecognizer(dismissGesture)
}

对于shouldDismiss函数:

@objc private func shouldDismiss() {
    alertController.dismiss(animated: true)
}

1
    UIView *alertView = self.alertController.view;
UIView *superPuperView = self.alertController.view.superview;
CGPoint tapCoord = [tap locationInView:superPuperView];
if (!CGRectContainsPoint(alertView.frame, tapCoord)) {
    //dismiss alert view
}

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