Presenting a UIAlertController properly on an iPad using iOS 8
Asked Answered
W

15

221

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?

Wilda answered 14/6, 2014 at 22:38 Comment(0)
C
309

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
}
Convent answered 15/6, 2014 at 20:42 Comment(3)
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.Contagious
Cancel button is not displaying in iPadMoulding
@BhavinRamani Cancel buttons are removed from popovers automatically, because tapping outside the popover represents "cancel", in a popover context.Ioyal
R
120

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 barButtonItem
  • or a sourceView and 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];
Resnatron answered 4/9, 2014 at 6:31 Comment(4)
It's not "one of the three" anchor point properties; it is: "either a sourceView and sourceRect or a barButtonItem".Whitewall
+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/…Continuity
Oh, man. It just crashed without any log message. Why would not at least provide compile-time warning (for universal apps)?Tintinnabulum
In the above code, if you specify barButtonItem, there's no need to specify sourceView, it has no effect.Caban
C
89

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.

Corunna answered 15/11, 2015 at 23:50 Comment(0)
C
49

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

Concurrence answered 25/2, 2020 at 21:6 Comment(5)
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 == .padCoincident
One disadvantage of this approach is that the alert isn't dismissable when clicking outside of its boundsLicense
Nice Solution! Thanks!Doughty
Boom! Nice and simple! Thanks!Sepulchre
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.Coadunate
O
31

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")
    }
Osrock answered 12/11, 2017 at 4:4 Comment(2)
I'm using drawer, I try to use the given solution but failed.Romain
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.Romain
P
24

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)
Piton answered 28/2, 2019 at 18:40 Comment(3)
I try it but not able to call func addActionSheerForiPad in xcode 11.2.1Romain
@RanaAliWaseem are u calling this inside UIViewController class?Coincident
yes. I call it in UIViewController class. But It is inherited with a Base class and Base Class inherited from UIViewController.Romain
L
16

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.

Loath answered 18/6, 2018 at 11:48 Comment(2)
Worked perfectly on both iPad & iPhone - ThanksDilly
It is not the best solution. Sometimes you want to use actionSheet style, which is modern.Coincident
H
14

Swift 4.2 You can use condition like that:

let alert = UIAlertController(title: nil, message: nil, preferredStyle: UIDevice.current.userInterfaceIdiom == .pad ? .alert : .actionSheet)
Hirohito answered 28/9, 2020 at 13:18 Comment(0)
C
9

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 = []
Co answered 7/3, 2022 at 0:44 Comment(0)
C
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];
Chronon answered 5/10, 2014 at 21:2 Comment(2)
This question is about UIAlertController, not UIActivityViewControllerSanderling
Can you update the answer for Swift 3 along with UIActivityViewController?Priceless
J
5

For me I just needed to add the following:

if let popoverController = alertController.popoverPresentationController {
    popoverController.barButtonItem = navigationItem.rightBarButtonItem
}
Jermyn answered 9/1, 2019 at 23:21 Comment(1)
You can omit the if statement and use optional chaining: alertController.popoverPresentationController?.barButtonItem = navigationItem.rightBarButtonItemMetralgia
E
2

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 = []
}
Etrem answered 16/7, 2019 at 11:30 Comment(0)
T
2

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)
}
Tanta answered 9/2, 2021 at 12:41 Comment(1)
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)Cypriot
V
0

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!

Vieira answered 30/12, 2021 at 22:21 Comment(0)
M
0

If you have tabbar, you can use it on iPad

if UIDevice.current.userInterfaceIdiom == .pad {
    alert.popoverPresentationController?.sourceView = self.tabBarController?.tabBar ?? UIView()
}
Manfred answered 13/6, 2023 at 18:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.