Swift UIAlertController -> ActionSheet iPad iOS8 Crashes

32,606

Solution 1

The error message is telling you that you need to give the alert controller's popoverPresentationController a location so that it can position itself properly. This is easy to do -- just check to see if there's a popover controller and add the sender as the source.

If your button is a UIBarButtonItem:

if let popoverController = alertController.popoverPresentationController {
    popoverController.barButtonItem = sender
}
self.presentViewController(alertController, animated: true, completion: nil)

Otherwise:

if let popoverController = alertController.popoverPresentationController {
    popoverController.sourceView = sender
    popoverController.sourceRect = sender.bounds
}
self.presentViewController(alertController, animated: true, completion: nil)

Solution 2

try this

alertController.popoverPresentationController?.sourceView = self.view

Solution 3

If you want to present it in the centre with no arrows on iPads [Swift 3+]:

    if let popoverController = alertController.popoverPresentationController {
        popoverController.sourceView = self.view
        popoverController.sourceRect = CGRect(x: self.view.bounds.midX, y: self.view.bounds.midY, width: 0, height: 0)
        popoverController.permittedArrowDirections = []
    }
    self.present(alertController, animated: true, completion: nil)

Solution 4

Nate Cook is totally right however I would do it so I detect if it is iPad or iPhone.

This is for barButtonItem:

if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad ){

            if let currentPopoverpresentioncontroller = alertController.popoverPresentationController{
                currentPopoverpresentioncontroller.barButtonItem = sender as! UIBarButtonItem
                currentPopoverpresentioncontroller.permittedArrowDirections = UIPopoverArrowDirection.down;
                self.present(alertController, animated: true, completion: nil)
            }
        }else{
            self.present(alertController, animated: true, completion: nil)
        }

Solution 5

var actionSheet = UIAlertController(title: "Please Select Camera or Photo Library", message: "", preferredStyle: .actionSheet)

if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad ){
    actionSheet = UIAlertController(title: "Please Select Camera or Photo Library", message: "", preferredStyle: .alert)
}

actionSheet.addAction(UIAlertAction(title: "Upload a Photo", style: .default, handler: { (UIAlertAction) in
    self.openPhotoLibrary()
}))
actionSheet.addAction(UIAlertAction(title: "Take a Photo", style: .default, handler: { (UIAlertAction) in
    self.openCamera()
}))
actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(actionSheet, animated: true, completion: nil)
Share:
32,606
Fabian Boulegue
Author by

Fabian Boulegue

Nerd Geek Videogamer Mac Developer Web Developer iOS Developer Tattoo

Updated on January 06, 2020

Comments

  • Fabian Boulegue
    Fabian Boulegue over 4 years

    currently i'm running into big trouble with my ActionSheet. On iPhone it works great, but on iPad it only crashes

    I create a new project with only one button

    import UIKit
    
    extension ViewController : UIActionSheetDelegate {
    
        func actionSheet(actionSheet: UIActionSheet, didDismissWithButtonIndex buttonIndex: Int) {
    
            if actionSheet.tag == 0 {
                if buttonIndex == 1 {
                    // doing something for "product page"
                } else if (buttonIndex == 2) {
                    // doing something for "video"
                }
            }
        }
    
    }
    
    class ViewController: UIViewController, UIActionSheetDelegate {
        @IBAction func test(sender: AnyObject) {
    
            let systemVersion: NSInteger = (UIDevice.currentDevice().systemVersion as NSString).integerValue
            if systemVersion < 8 {
                // iOS7:
                let action:UIActionSheet = UIActionSheet(title: "Change Map Type", delegate: self, cancelButtonTitle: "Back", destructiveButtonTitle: nil, otherButtonTitles: "Product Page", "Video")
                action.tag = 0
                action.showInView(self.view)
            } else {
                // iOS8:
                let alertController: UIAlertController = UIAlertController(title: "Change Map Type", message: nil, preferredStyle: UIAlertControllerStyle.ActionSheet)
                let cancelAction: UIAlertAction = UIAlertAction(title: "Back", style: UIAlertActionStyle.Cancel, handler: nil)
                let button1action: UIAlertAction = UIAlertAction(title: "Product Page", style: UIAlertActionStyle.Default, handler: { (action: UIAlertAction!) -> () in
                    // doing something for "product page"
                })
                let button2action: UIAlertAction = UIAlertAction(title: "Video", style: UIAlertActionStyle.Default, handler: { (action: UIAlertAction!) -> () in
                    // doing something for "video"
                })
                alertController.addAction(cancelAction)
                alertController.addAction(button1action)
                alertController.addAction(button2action)
    
                self.presentViewController(alertController, animated: true, completion: nil)
            }
        }
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
        }
    
    }
    

    As i said on the iphone it works, but if i click the button on iPad the App crashes with

    2014-09-25 14:54:52.784 test[9541:1970048] * Terminating app due to uncaught exception 'NSGenericException', reason: 'Your application has presented a UIAlertController () 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.' * First throw call stack: ( 0 CoreFoundation 0x00613df6 exceptionPreprocess + 182 1 libobjc.A.dylib
    0x01fdaa97 objc_exception_throw + 44 2 UIKit
    0x0164da37 -[UIPopoverPresentationController presentationTransitionWillBegin] + 3086 3 UIKit
    0x00f54f75 __71-[UIPresentationController _initViewHierarchyForPresentationSuperview:]_block_invoke + 1666 4 UIKit 0x00f53554 __56-[UIPresentationController runTransitionForCurrentState]_block_invoke + 226 5 UIKit
    0x00f8721b __40+[UIViewController _scheduleTransition:]_block_invoke + 18 6 UIKit 0x00e4d62e ___afterCACommitHandler_block_invoke + 15 7 UIKit 0x00e4d5d9 _applyBlockToCFArrayCopiedToStack + 415 8 UIKit
    0x00e4d3ee _afterCACommitHandler + 545 9 CoreFoundation
    0x00536fbe __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
    + 30 10 CoreFoundation 0x00536f00 __CFRunLoopDoObservers + 400 11 CoreFoundation 0x0052c93a __CFRunLoopRun + 1226 12 CoreFoundation 0x0052c1ab CFRunLoopRunSpecific + 443 13 CoreFoundation
    0x0052bfdb CFRunLoopRunInMode + 123 14 GraphicsServices
    0x0438424f GSEventRunModal + 192 15 GraphicsServices
    0x0438408c GSEventRun + 104 16 UIKit
    0x00e23e16 UIApplicationMain + 1526 17 test
    0x00085e9e top_level_code + 78 18 test
    0x00085edb main + 43 19 libdyld.dylib
    0x0273eac9 start + 1 20 ???
    0x00000001 0x0 + 1 ) libc++abi.dylib: terminating with uncaught exception of type NSException

    Project can be found at https://www.dropbox.com/s/54jqd8nsc67ll5g/test.zip?dl=0 for download and try.

  • Xerion
    Xerion about 9 years
    with swift 1.2, it's now required to do: popoverController.sourceView = sender as! UIView
  • Ashok R
    Ashok R almost 8 years
    popoverController.sourceRect = sender.bounds makes ActionSheet to appear on top left corner of the iPad. Instead you can use popoverController.sourceRect = sender.frame
  • leafcutter
    leafcutter over 6 years
    sender.bounds works ok for me - sourceRect is 'The rectangle in the specified view in which to anchor the popover'. I take that to mean it is in the coordinate space of the sourceView and hence .bounds
  • Dale
    Dale almost 6 years
    Something like this solved my problem. I did the .barButtonItem = sender assignment, but instead of permittedArrowDirections, I used .sourceRect = sender.accessibilityFrame.