Present a UIAlertController from within a Popover in iOS8
Solution 1
Found it ! If this AlertController is presented inside a popover, it must provide the location information, either a sourceView and sourceRect, or a barButtonItem.
Like
resetWarning.popoverPresentationController?.sourceView = selectedCell?.contentView
resetWarning.popoverPresentationController?.sourceRect = selectedCell!.contentView.frame
My code had to look like that:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var previouslySelectedCell: UITableViewCell?
if checkedIndexPath != nil {
previouslySelectedCell = tableView.cellForRowAtIndexPath(checkedIndexPath)
}
var selectedCell = tableView.cellForRowAtIndexPath(indexPath)
let selectedCurrency = PortfolioCurrencyStore.sharedStore.allCurrencies[indexPath.row]
if selectedCurrency.symbol != GlobalSettings.sharedStore.portfolioCurrency {
// Warning : changing the portfolio currency will reset the portfolio
var resetWarning = UIAlertController(title: NSLocalizedString("Currency Picker VC:AS title", comment: "Changing currency will reset portfolio"), message: nil, preferredStyle: .ActionSheet)
// destructive button
let resetAction = UIAlertAction(title: NSLocalizedString("Currency Picker VC:AS destructive", comment: "Destructive button title"), style: .Destructive, handler: { (action: UIAlertAction!) in
// Remove checkmark from the previously marked cell
previouslySelectedCell?.accessoryType = .None
// Add checkmark to the selected cell
selectedCell?.accessoryType = .Checkmark
self.checkedIndexPath = indexPath
// Animate deselection of cell
self.tableView.deselectRowAtIndexPath(indexPath, animated:true)
// Stock the portfolio currency as NSUserDefaults
GlobalSettings.sharedStore.portfolioCurrency = selectedCurrency.symbol // link between portfolioCurrency as a String and currency.symbol as the property of a Currency instance.
// Delete all items from the StockStore
StockStore.sharedStore.removeAllStocks()
println("StockStore : all entries were deleted")
// Delete all items from the CurrencyRateStore
CurrencyRateStore.sharedStore.deleteAllRates()
println("CurrencyStore : all entries were deleted")
// Delete all items from the SalesJournal
SalesJournal.sharedStore.removeAllEntries()
println("SalesJournal : all Sales journal entries were deleted")
// Reload tableView
self.tableView.reloadData()
// On Regular sizes, the currency picker is presented inside a popover : reloadData of the List View
NSNotificationCenter.defaultCenter().postNotificationName("CurrencyPickerVC_PortfolioCurrencyDidChangeNotification", object:nil, userInfo:nil)
// Animate deselection of cell
tableView.deselectRowAtIndexPath(indexPath, animated:true)
// Return to root VC
self.navigationController?.popToRootViewControllerAnimated(true)
})
// cancel button
let cancelAction = UIAlertAction(title: NSLocalizedString("Currency Picker VC:AS cancel", comment: "Cancel button title"), style: .Cancel, handler: { (alertAction: UIAlertAction!) -> Void in
// Animate deselection of cell
self.tableView.deselectRowAtIndexPath(indexPath, animated:true)
})
resetWarning.addAction(resetAction)
resetWarning.addAction(cancelAction)
// If this AlertController is presented inside a popover, it must provide the location information, either a sourceView and sourceRect or a barButtonItem.
resetWarning.popoverPresentationController?.sourceView = selectedCell?.contentView
resetWarning.popoverPresentationController?.sourceRect = selectedCell!.contentView.frame
presentViewController(resetWarning, animated: true, completion: nil)
} else {
// Animate deselection of cell
tableView.deselectRowAtIndexPath(indexPath, animated:true)
}
}
Now the image looks like this:
Solution 2
I had the same problem and wasn't able to figure out how to prevent the popover from resizing. Using an Alert instead of an Action Sheet will also cause the popover to resize. The workaround I found was to use an Action Sheet as a popover itself by setting the Modal Presentation Style to UIModalPresentationPopover
. I know you're using Swift but my code is Objective-C; hopefully it will be easy for you to translate:
- (UIAlertController *)modalAlertWithTitle:(NSString *)title andMessage:(NSString *)message fromViewController:(UIViewController *)sender {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleActionSheet];
// This will turn the Action Sheet into a popover
[alertController setModalPresentationStyle:UIModalPresentationPopover];
// Set Modal In Popover to YES to make sure your popover isn't dismissed by taps outside the popover controller
[alertController setModalInPopover:YES];
// Get the PopoverPresentationController and set the source View and Rect so the Action Sheet knows where to pop up
UIPopoverPresentationController *popPresenter = [alertController popoverPresentationController];
popPresenter.sourceView = sender.view;
popPresenter.sourceRect = sender.view.bounds;
return alertController;
}
It's very important that you remember to set your cancel button's UIAlertAction style to Default. If you set the style to Cancel it won't appear on the action sheet since this uses a ModalPresentationPopover. Users also won't be able to cancel by tapping outside the Action Sheet since we set ModalInPopover to YES. Setting the cancel button's style to Default will ensure it appears on the sheet.
I just made this as a utility method in my AppDelegate so I could call it from all of my popovers. This works but isn't really an ideal solution because if something causes an Alert to fire while one of your popovers is up, it might get resized. Please let me know if you figure out how to prevent the resizing from occurring at all. Best of luck!
Frederic Adda
I have been an iOS developer since 2012 (and before that a functional SAP consultant for 15 years). I like simple, elegant UI, and I care a lot about the little details that make a great UX. I currently live in Ra'anana, Israel. Since May 2022 I work for Nexar in Tel-Aviv, where we develop a solution to make roads safer (with our dash cams). At Fiverr in Tel-Aviv for 2 years between 2020 and 2022. Before that, I worked in France as a freelance iOS developer on the following customer apps: BNP Paribas, the app for one of the major French banks, Le livre scolaire, a series of college books reinvented for iPad REVapp, an app to determine the dosage of a drug to cure lymphoma. Zenmob, an app for the transportation & logistics group Geodis Smart Pillow iX21, an companion app for a Bluetooth-connected pillow A few of my personal applications on the app Store: ZEN Portfolio, a simple yet efficient stocks management app (update: removed from the store after Yahoo killed their Finance API) ZEN BabyBook, an app for young kids where images of animals, fruits, vegetables, etc are spelled in more than 15 different languages ZEN Speller, an app to spell out numbers in plain text, in several languages. Scales & Modes calculator, an app for music theory lovers.
Updated on June 04, 2022Comments
-
Frederic Adda about 2 years
I set a UITableViewController to be displayed in a popover on iPad :
When I click on a row, I display an alert to warn the user of a potential destructive action. I used the new UIAlertController, and here is what happens:
The popover becomes very small (the size of the alertController view in fact). If I press Cancel, I can see the result :
Here is my code:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { var previouslySelectedCell: UITableViewCell? if checkedIndexPath != nil { previouslySelectedCell = tableView.cellForRowAtIndexPath(checkedIndexPath) } var selectedCell = tableView.cellForRowAtIndexPath(indexPath) let selectedCurrency = PortfolioCurrencyStore.sharedStore().allCurrencies[indexPath.row] if selectedCurrency.symbol != GlobalSettings.sharedStore().portfolioCurrency { // Warning : changing the portfolio currency will reset the portfolio var resetWarning = UIAlertController(title: NSLocalizedString("Currency Picker VC:AS title", comment: "Changing currency will reset portfolio"), message: nil, preferredStyle: .ActionSheet) // destructive button let resetAction = UIAlertAction(title: NSLocalizedString("Currency Picker VC:AS destructive", comment: "Destructive button title"), style: .Destructive, handler: { (action: UIAlertAction!) in // Remove checkmark from the previously marked cell previouslySelectedCell?.accessoryType = .None // Add checkmark to the selected cell selectedCell?.accessoryType = .Checkmark self.checkedIndexPath = indexPath // Animate deselection of cell self.tableView.deselectRowAtIndexPath(indexPath, animated:true) // Stock the portfolio currency as NSUserDefaults GlobalSettings.sharedStore().portfolioCurrency = selectedCurrency.symbol // link between portfolioCurrency as a String and currency.symbol as the property of a Currency instance. // Delete all items from the StockStore StockStore.sharedStore().removeAllStocks() println("StockStore : all entries were deleted") // Reload tableView self.tableView.reloadData() }) // cancel button let cancelAction = UIAlertAction(title: NSLocalizedString("Currency Picker VC:AS cancel", comment: "Cancel button title"), style: .Cancel, handler:nil) resetWarning.addAction(resetAction) resetWarning.addAction(cancelAction) presentViewController(resetWarning, animated: true, completion: nil) } else { // Animate deselection of cell tableView.deselectRowAtIndexPath(indexPath, animated:true) } }
Did I miss something ?
Thanks for your help