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?
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
}
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];
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 (
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
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")
}
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)
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.
Swift 4.2 You can use condition like that:
let alert = UIAlertController(title: nil, message: nil, preferredStyle: UIDevice.current.userInterfaceIdiom == .pad ? .alert : .actionSheet)
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];
For me I just needed to add the following:
if let popoverController = alertController.popoverPresentationController {
popoverController.barButtonItem = navigationItem.rightBarButtonItem
}
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 = []
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 = []
}
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)
}
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!
Success story sharing