在使用iOS 8的iPad上正确呈现UIAlertController

219

iOS 8.0推出了UIAlertController来替代UIActionSheet。不幸的是,Apple没有添加任何关于如何呈现它的信息。我在hayaGeek的博客中找到了一个示例,但在iPad上似乎无法正常工作,视图完全错位:

错位: 错位图片

正确: 输入图像描述

我使用以下代码将其展示在界面上:

    let alert = UIAlertController()
    // setting buttons
    self.presentModalViewController(alert, animated: true)

有没有其他方法可以在iPad上添加它?或者苹果只是忘记了iPad,还是尚未实现?

15个回答

309

您可以使用 UIPopoverPresentationController 来从弹出窗口中展示一个 UIAlertController

在Obj-C中:

您可以使用 UIPopoverPresentationController 从弹出视图中呈现一个 UIAlertController

UIViewController *self; // code assumes you're in a view controller
UIButton *button; // the button you want to show the popup sheet from

UIAlertController *alertController;
UIAlertAction *destroyAction;
UIAlertAction *otherAction;

alertController = [UIAlertController alertControllerWithTitle:nil
                                                      message:nil
                           preferredStyle:UIAlertControllerStyleActionSheet];
destroyAction = [UIAlertAction actionWithTitle:@"Remove All Data"
                                         style:UIAlertActionStyleDestructive
                                       handler:^(UIAlertAction *action) {
                                           // do destructive stuff here
                                       }];
otherAction = [UIAlertAction actionWithTitle:@"Blah"
                                       style:UIAlertActionStyleDefault
                                     handler:^(UIAlertAction *action) {
                                         // do something here
                                     }];
// note: you can control the order buttons are shown, unlike UIActionSheet
[alertController addAction:destroyAction];
[alertController addAction:otherAction];
[alertController setModalPresentationStyle:UIModalPresentationPopover];

UIPopoverPresentationController *popPresenter = [alertController 
                                              popoverPresentationController];
popPresenter.sourceView = button;
popPresenter.sourceRect = button.bounds;
[self presentViewController:alertController animated:YES completion:nil];

虽然有很多关于Swift 4.2的博客可供参考,但在这里编辑可能可以节省您的时间。

if let popoverController = yourAlert.popoverPresentationController {
    popoverController.sourceView = self.view //to set the source of your alert
    popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0) // you can set this as per your requirement.
    popoverController.permittedArrowDirections = [] //to hide the arrow of any particular direction
}

如果你看不到文本,请使用[alertController.view setTintColor:[UIColor blackColor]];UIAlertController默认使用窗口的着色颜色,这在本例中可能是白色和不可见的。 - whyoz
3
iPad上未显示取消按钮。 - Bhavin Ramani
16
在弹出窗口中,自动移除取消按钮,因为在此上下文中,点击弹出窗口外部即代表"取消"。 - christopherdrum
太棒了,我的问题解决了!非常感谢你! - Mahir Tayir

120

在iPad上,使用新的UIPopoverPresentationController,警报将显示为弹出窗口,需要指定一个锚点来呈现弹出窗口,可以使用以下方法之一:

  • barButtonItem
  • sourceView和sourceRect

为了指定锚点,您需要获取对UIAlertController的UIPopoverPresentationController的引用,并设置其中一个属性如下:

alertController.popoverPresentationController.barButtonItem = button;

示例代码:

UIAlertAction *actionDelete = nil;
UIAlertAction *actionCancel = nil;

// create action sheet
UIAlertController *alertController = [UIAlertController
                                      alertControllerWithTitle:actionTitle message:nil
                                      preferredStyle:UIAlertControllerStyleActionSheet];

// Delete Button
actionDelete = [UIAlertAction
                actionWithTitle:NSLocalizedString(@"IDS_LABEL_DELETE", nil)
                style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) {
                    
                    // Delete
                    // [self deleteFileAtCurrentIndexPath];
                }];

// Cancel Button
actionCancel = [UIAlertAction
                actionWithTitle:NSLocalizedString(@"IDS_LABEL_CANCEL", nil)
                style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
                    // cancel
                    // Cancel code
                }];

// Add Cancel action
[alertController addAction:actionCancel];
[alertController addAction:actionDelete];

// show action sheet
alertController.popoverPresentationController.barButtonItem = button;
alertController.popoverPresentationController.sourceView = self.view;

[self presentViewController:alertController animated:YES
                 completion:nil];

31
它不是“三个之一”的锚点属性;它是:“要么是sourceView和sourceRect,要么是barButtonItem”。 - Rolleric
2
+1 for Rolleric. 苹果公司的文档中关于sourceRect的说明是:“与sourceView属性一起使用此属性,以指定弹出窗口的锚定位置。或者,您可以使用barButtonItem属性指定弹出窗口的锚定位置。”- https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIPopoverPresentationController_class/index.html#//apple_ref/occ/instp/UIPopoverPresentationController/sourceRect - Ben Patch
哦,天啊。它竟然没有任何日志信息就崩溃了。为什么不能至少提供编译时警告(对于通用应用程序)? - Mike Keskinov
在上面的代码中,如果您指定了barButtonItem,则无需指定sourceView,因为它没有任何影响。 - mrzzmr

88

在 Swift 2 中,您想要执行类似以下的操作才能在 iPhone 和 iPad 上正确显示:

func confirmAndDelete(sender: AnyObject) {
    guard let button = sender as? UIView else {
        return
    }

    let alert = UIAlertController(title: NSLocalizedString("Delete Contact?", comment: ""), message: NSLocalizedString("This action will delete all downloaded audio files.", comment: ""), preferredStyle: .ActionSheet)
    alert.modalPresentationStyle = .Popover

    let action = UIAlertAction(title: NSLocalizedString("Delete", comment: ""), style: .Destructive) { action in
        EarPlaySDK.deleteAllResources()
    }
    let cancel = UIAlertAction(title: NSLocalizedString("Cancel", comment: ""), style: .Cancel) { action in

    }
    alert.addAction(cancel)
    alert.addAction(action)

    if let presenter = alert.popoverPresentationController {
        presenter.sourceView = button
        presenter.sourceRect = button.bounds
    }
    presentViewController(alert, animated: true, completion: nil)
}

如果您不设置presentor,那么在iPad上的-[UIPopoverPresentationController presentationTransitionWillBegin]方法中会出现异常,错误信息如下:

Fatal Exception: NSGenericException 您的应用程序已经展示了一个样式为UIAlertControllerStyleActionSheet的UIAlertController (<UIAlertController: 0x17858a00>)。具有此样式的UIAlertController的modalPresentationStyle是UIModalPresentationPopover。对于这个popover,您必须通过警报控制器的popoverPresentationController提供位置信息。您必须提供sourceView和sourceRect或barButtonItem之一。如果在呈现警报控制器时不知道此信息,则可以在UIPopoverPresentationControllerDelegate方法-prepareForPopoverPresentation中提供它。


46

Swift 5

我在 iPhone 上使用了 "actionsheet" 样式,在 iPad 上使用了 "alert" 样式。iPad 显示在屏幕中央,无需指定 sourceView 或将视图固定在任何位置。

var alertStyle = UIAlertController.Style.actionSheet
if (UIDevice.current.userInterfaceIdiom == .pad) {
  alertStyle = UIAlertController.Style.alert
}

let alertController = UIAlertController(title: "Your title", message: nil, preferredStyle: alertStyle)

编辑:根据ShareToD的建议,更新了过时的“ UI_USER_INTERFACE_IDIOM()== UIUserInterfaceIdiom.pad”检查


2
在iOS 13中,“UI_USER_INTERFACE_IDIOM()”已经被弃用于iOS 13.0:直接使用-[UIDevice userInterfaceIdiom]。您应将其更改为UIDevice.current.userInterfaceIdiom == .pad。 - ShadeToD
这种方法的一个缺点是,当在其范围之外单击时,警报无法被解除。 - mliu
1
很好的解决方案!谢谢! - Pressing_Keys_24_7
瞧!简单易懂!谢谢! - sprut
几乎完美 :) 如果您正在尝试将alertController用作类属性,则无法使用“alertStyle”作为参数,因为它尚未初始化。相反,我建议使用三元运算符来检查当前设备是否为.pad,并根据此决定是否应使用.actionSheet或.alert样式。 - Vladimir Sukanica

31

针对Swift 3.0及以上版本的更新

    let actionSheetController: UIAlertController = UIAlertController(title: "SomeTitle", message: nil, preferredStyle: .actionSheet)

    let editAction: UIAlertAction = UIAlertAction(title: "Edit Details", style: .default) { action -> Void in

        print("Edit Details")
    }

    let deleteAction: UIAlertAction = UIAlertAction(title: "Delete Item", style: .default) { action -> Void in

        print("Delete Item")
    }

    let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .cancel) { action -> Void in }

    actionSheetController.addAction(editAction)
    actionSheetController.addAction(deleteAction)
    actionSheetController.addAction(cancelAction)

//        present(actionSheetController, animated: true, completion: nil)   // doesn't work for iPad

    actionSheetController.popoverPresentationController?.sourceView = yourSourceViewName // works for both iPhone & iPad

    present(actionSheetController, animated: true) {
        print("option menu presented")
    }

我正在使用抽屉(drawer),尝试使用给定的解决方案,但失败了。 - Rana Ali Waseem
我没有代码,因为我删除了操作表并使用警报。 但在我的代码中只有一行不同 let actionSheet = UIAlertController(title: "",, message: "", preferredStyle: .actionSheet ) 但我记得日志,它由于抽屉而崩溃,我认为抽屉阻止打开操作表。 因为它只在屏幕的左上角打开。 问题仅出现在iPad上。 - Rana Ali Waseem

24

Swift 4及以上版本

我已经创建了一个扩展

extension UIViewController {
  public func addActionSheetForiPad(actionSheet: UIAlertController) {
    if let popoverPresentationController = actionSheet.popoverPresentationController {
      popoverPresentationController.sourceView = self.view
      popoverPresentationController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
      popoverPresentationController.permittedArrowDirections = []
    }
  }
}

使用方法:

let actionSheetVC = UIAlertController(title: "Title", message: nil, preferredStyle: .actionSheet)
addActionSheetForiPad(actionSheet: actionSheetVC)
present(actionSheetVC, animated: true, completion: nil)

我尝试过了,但在Xcode 11.2.1中无法调用addActionSheerForiPad函数。 - Rana Ali Waseem
@RanaAliWaseem 你是在 UIViewController 类中调用这个吗? - ShadeToD
是的,我在UIViewController类中调用它。但它是从一个基类继承而来,而基类又是从UIViewController继承而来的。 - Rana Ali Waseem

16

2018年更新

我的一个应用因为这个原因被拒绝,解决方法是将操作表改为警告框。

非常有效,并且通过了App Store的测试。

可能不适合每个人,但我希望这能够帮助一些人快速摆脱困境。


1
在iPad和iPhone上都完美运行 - 谢谢 - Jeremy Andrews
这不是最好的解决方案。有时你想要使用现代的 actionSheet 样式。 - ShadeToD

13

Swift 4.2 您可以使用以下条件:

let alert = UIAlertController(title: nil, message: nil, preferredStyle: UIDevice.current.userInterfaceIdiom == .pad ? .alert : .actionSheet)

9

Swift 5

适用于iPad和iPhone。

操作表弹出在按钮下面

对于iPhone,操作表将如常显示;对于iPad,它会显示在按钮下方。

actionSheet.popoverPresentationController?.sourceView = fooButton
actionSheet.popoverPresentationController?.sourceRect = fooButton.bounds
居中显示的操作表

在iPhone上,操作表将像常规情况下一样显示;而在iPad上,它将居中显示在屏幕/视图中。

actionSheet.popoverPresentationController?.sourceView = view
actionSheet.popoverPresentationController?.sourceRect = CGRect(x: view.bounds.midX, y: view.bounds.midY, width: 0, height: 0)
actionSheet.popoverPresentationController?.permittedArrowDirections = []

8
这里有一个快速解决方案:
NSString *text = self.contentTextView.text;
NSArray *items = @[text];

UIActivityViewController *activity = [[UIActivityViewController alloc]
                                      initWithActivityItems:items
                                      applicationActivities:nil];

activity.excludedActivityTypes = @[UIActivityTypePostToWeibo];

if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
    //activity.popoverPresentationController.sourceView = shareButtonBarItem;

    activity.popoverPresentationController.barButtonItem = shareButtonBarItem;

    [self presentViewController:activity animated:YES completion:nil];

}
[self presentViewController:activity animated:YES completion:nil];

3
请提供需要翻译的上下文,以便我更好地完成翻译任务。 - Roman Truba
你能否更新Swift 3的答案以及UIActivityViewController? - Dia

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