Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Present a UIAlertController from within a Popover in iOS8

I set a UITableViewController to be displayed in a popover on iPad : UITableViewController inside a popover

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: UIAlertViewController appears ...

The popover becomes very small (the size of the alertController view in fact). If I press Cancel, I can see the result : ... making the popover shrink!

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

like image 450
Frédéric Adda Avatar asked Sep 12 '14 09:09

Frédéric Adda


2 Answers

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: enter image description here

like image 50
Frédéric Adda Avatar answered Oct 14 '22 15:10

Frédéric Adda


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!

like image 5
lxmmxl56 Avatar answered Oct 14 '22 14:10

lxmmxl56