'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:

Misplaced: Misplaced image

Correct: enter image description here

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?



Solution 1:[1]

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

Solution 2:[2]

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 (<UIAlertController: 0x17858a00>) 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.

Solution 3:[3]

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

Solution 4:[4]

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

Solution 5:[5]

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)

Solution 6:[6]

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.

Solution 7:[7]

Swift 4.2 You can use condition like that:

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

Solution 8:[8]

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

Solution 9:[9]

For me I just needed to add the following:

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

Solution 10:[10]

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

Solution 11:[11]

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

Solution 12:[12]

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

Solution 13:[13]

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

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."
  • 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!

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Pang
Solution 3
Solution 4 iAj
Solution 5 Jonah Katz
Solution 6 SongBox
Solution 7 Ahmed
Solution 8 Zoey Mertes
Solution 9 shim
Solution 10 shim
Solution 11 Ali Aqdas
Solution 12
Solution 13 Rinni