ChatGPT解决这个技术问题 Extra ChatGPT

Presenting a UIAlertController properly on an iPad using iOS 8

With iOS 8.0, Apple introduced UIAlertController to replace UIActionSheet. Unfortunately, Apple didn't add any information on how to present it. I found an entry about it on hayaGeek's blog, however, it doesn't seem to work on iPad. The view is totally misplaced:

https://i.stack.imgur.com/KhAaj.png

https://i.stack.imgur.com/lFd3S.png

I use the following code to show it on the interface:

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

Is there another way to add it for iPad? Or did Apple just forget the iPad, or not implemented, yet?


M
Marcus Adams

You can present a UIAlertController from a popover by using UIPopoverPresentationController.

In Obj-C:

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

Editing for Swift 4.2, though there are many blogs available for the same but it may save your time to go and search for them.

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
}

Use [alertController.view setTintColor:[UIColor blackColor]]; if you don't see the text. UIAlertController uses the window's tint color by default, which might be white and invisible in this example.
Cancel button is not displaying in iPad
@BhavinRamani Cancel buttons are removed from popovers automatically, because tapping outside the popover represents "cancel", in a popover context.
this is awesome , my problem is solved ! thank you so much!
S
Sabareesh

On iPad the alert will be displayed as a popover using the new UIPopoverPresentationController, it requires that you specify an anchor point for the presentation of the popover using either a sourceView and sourceRect or a barButtonItem

barButtonItem

sourceView

sourceRect

In order to specify the anchor point you will need to obtain a reference to the UIAlertController's UIPopoverPresentationController and set one of the properties as follows:

alertController.popoverPresentationController.barButtonItem = button;

sample code:

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

It's not "one of the three" anchor point properties; it is: "either a sourceView and sourceRect or a barButtonItem".
+1 for Rolleric. Apple's documentation states concerning sourceRect: "Use this property in conjunction with the sourceView property to specify the anchor location for the popover. Alternatively, you may specify the anchor location for the popover using the barButtonItem property." - developer.apple.com/library/prerelease/ios/documentation/UIKit/…
Oh, man. It just crashed without any log message. Why would not at least provide compile-time warning (for universal apps)?
In the above code, if you specify barButtonItem, there's no need to specify sourceView, it has no effect.
P
Pang

In Swift 2, you want to do something like this to properly show it on iPhone and 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)
}

If you don't set the presenter, you will end up with an exception on iPad in -[UIPopoverPresentationController presentationTransitionWillBegin] with the following message:

Fatal Exception: NSGenericException Your application has presented a UIAlertController () of style UIAlertControllerStyleActionSheet. The modalPresentationStyle of a UIAlertController with this style is UIModalPresentationPopover. You must provide location information for this popover through the alert controller's popoverPresentationController. You must provide either a sourceView and sourceRect or a barButtonItem. If this information is not known when you present the alert controller, you may provide it in the UIPopoverPresentationControllerDelegate method -prepareForPopoverPresentation.


G
Greg T

Swift 5

I used "actionsheet" style for iPhone and "alert" for iPad. iPad displays in the center of the screen. No need to specify sourceView or anchor the view anywhere.

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

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

Edit: Per ShareToD's suggestion, updated deprecated "UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad" check


in iOS 13 'UI_USER_INTERFACE_IDIOM()' was deprecated in iOS 13.0: Use -[UIDevice userInterfaceIdiom] directly. You should change it to UIDevice.current.userInterfaceIdiom == .pad
One disadvantage of this approach is that the alert isn't dismissable when clicking outside of its bounds
Nice Solution! Thanks!
Boom! Nice and simple! Thanks!
Close to perfect :) If you are trying to use alertController as class property, you can't use "alertStyle" as parameter, because it is not yet initialised. Instead I would recommend using ternary operator that checks if current device is .pad and by that decide if .actionSheet or .alert style should be used.
i
iAj

Update for Swift 3.0 and higher

    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")
    }

I'm using drawer, I try to use the given solution but failed.
I don't have code, because I remove action sheet and use alert. But in my code just one line was different let actionSheet = UIAlertController(title: "",, message: "", preferredStyle: .actionSheet ) But I remember the logs, It was crashing due to drawer, I think that drawer resist to open action sheet. because it was opening in left corner of screen. issue only was at iPad.
J
Jonah Katz

Swift 4 and above

I have created an extension

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 = []
    }
  }
}

How to use:

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

I try it but not able to call func addActionSheerForiPad in xcode 11.2.1
@RanaAliWaseem are u calling this inside UIViewController class?
yes. I call it in UIViewController class. But It is inherited with a Base class and Base Class inherited from UIViewController.
S
SongBox

2018 Update

I just had an app rejected for this reason and a very quick resolution was simply to change from using an action sheet to an alert.

Worked a charm and passed the App Store testers just fine.

May not be a suitable answer for everyone but I hope this helps some of you out of a pickle quickly.


Worked perfectly on both iPad & iPhone - Thanks
It is not the best solution. Sometimes you want to use actionSheet style, which is modern.
A
Ahmed

Swift 4.2 You can use condition like that:

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

thanks a lot. its worked for me
Z
Zoey Mertes

Here's a quick solution:

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

This question is about UIAlertController, not UIActivityViewController
Can you update the answer for Swift 3 along with UIActivityViewController?
s
shim

For me I just needed to add the following:

if let popoverController = alertController.popoverPresentationController {
    popoverController.barButtonItem = navigationItem.rightBarButtonItem
}

You can omit the if statement and use optional chaining: alertController.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItem
c
craft

Swift 5

Works for both iPad and iPhone.

Action sheet hovering below a button

For iPhone, action sheet will show as normal, and iPad will show below the button.

actionSheet.popoverPresentationController?.sourceView = fooButton
actionSheet.popoverPresentationController?.sourceRect = fooButton.bounds

Action sheet in the middle of the screen/view

For iPhone, action sheet will show as normal, and iPad will show in the middle of the screen/view.

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

s
shim

Just add the following code before presenting your action sheet:

if let popoverController = optionMenu.popoverPresentationController {
    popoverController.sourceView = self.view
    popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
    popoverController.permittedArrowDirections = []
}

A
Ali Aqdas

It will work for both iphone and ipad

func showImagePicker() {
    var alertStyle = UIAlertController.Style.actionSheet
    if (UIDevice.current.userInterfaceIdiom == .pad) {
      alertStyle = UIAlertController.Style.alert
    }
    let alert = UIAlertController(title: "", message: "Upload Attachment", preferredStyle: alertStyle)
    alert.addAction(UIAlertAction(title: "Choose from gallery", style: .default , handler:{ (UIAlertAction) in
        self.pickPhoto(sourceType: .photoLibrary)
    }))
    alert.addAction(UIAlertAction(title: "Take Photo", style: .default, handler:{ (UIAlertAction) in
        self.pickPhoto(sourceType: .camera)
    }))
    alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler:{ (UIAlertAction) in
    }))
    present(alert, animated: true, completion: nil)
}

Thats the way it works. The magic is in the lines: var alertStyle = UIAlertController.Style.actionSheet if (UIDevice.current.userInterfaceIdiom == .pad) { alertStyle = UIAlertController.Style.alert } let alert = UIAlertController(title: "", message: "Upload Attachment", preferredStyle: alertStyle)
R
Rinni

If your codebase is supporting both iPhone and iPad devices consider the following

Use present(_ viewControllerToPresent:animated:completion:) regularly when:

Presenting a UIAlertController with preferredStyle of .alert

Presenting a UIViewController with .modalPresentationStyle of: .overFullScreen .formSheet .automatic The default value if modalPresentationStyle is not specified .currentContext .fullScreen .custom .overCurrentContext

.overFullScreen

.formSheet

.automatic The default value if modalPresentationStyle is not specified

.currentContext

.fullScreen

.custom

.overCurrentContext

Configure the popoverPresentationController's sourceRect and sourceView before presenting when:

Presenting a UIAlertController with preferredStyle of .actionSheet

Presenting a UIViewController with .modalPresentationStyle of: .popover .none This will crash on both iPhone and iPads with the error "The specified modal presentation style doesn't have a corresponding presentation controller."

.popover

.none This will crash on both iPhone and iPads with the error "The specified modal presentation style doesn't have a corresponding presentation controller."

Presenting a UIActivityViewController (Based on https://developer.apple.com/documentation/uikit/uiactivityviewcontroller ; "On iPad, you must present the view controller in a popover. On iPhone and iPod touch, you must present it modally.")

Here's an example of configuring the popoverPresentationController

if let popoverController = popoverPresentationController {
  popoverController.sourceView = view
  popoverController.sourceRect = CGRect(x: view.bounds.maxX, y: 40, width: 0, height: 0)
}

Let me know if you find any other cases that aren’t outlined here!