Swift UIAlertController -> ActionSheet iPad iOS8 Crashes - iphone

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.

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)

try this
alertController.popoverPresentationController?.sourceView = self.view

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)

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)
}

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)

If you want show your alert in center and no arrow, I tryed this, it works on iOS 11.2
swift 4.2 version:
let actionSheet = UIAlertController ......
if ( UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiom.pad ){
if let currentPopoverpresentioncontroller = actionSheet.popoverPresentationController{
currentPopoverpresentioncontroller.permittedArrowDirections = []
currentPopoverpresentioncontroller.sourceRect = CGRect(x: (self.view.bounds.midX), y: (self.view.bounds.midY), width: 0, height: 0)
currentPopoverpresentioncontroller.sourceView = self.view
self.present(actionSheet, animated: true, completion: nil)
}
}else{
self.present(actionSheet, animated: true, completion: nil)
}
Objective C version:
UIAlertController* actionSheet
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
NSArray *empty;
UIPopoverPresentationController *currentPopOverPresentationController = [actionSheet popoverPresentationController];
currentPopOverPresentationController.permittedArrowDirections = empty;
currentPopOverPresentationController.sourceRect = CGRectMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds), 0, 0);
currentPopOverPresentationController.sourceView = self.view;
[self presentViewController:actionSheet animated:YES completion:nil];
}else{
[self presentViewController:actionSheet animated:YES completion:nil];
}

If someone uses sender : UITapGestureRecognizer this might be helpful.
#objc func popupSettings(sender : UITapGestureRecognizer) {
.....
if let popoverPresentationController = alert.popoverPresentationController {
popoverPresentationController.sourceView = self.view
popoverPresentationController.sourceRect = CGRect(origin: sender.location(in: self.view), size: CGSize(width: 1.0, height: 1.0))
}
self.present(alert, animated: true, completion: nil)
}

To follow up on Nate Cook's answer. If your button is a UIBarButtonItem it may be necessary to perform casting on sender.
if let popoverController = alertController.popoverPresentationController {
popoverController.barButtonItem = sender as! UIBarButtonItem
}
self.presentViewController(alertController, animated: true, completion: nil)

Related

How can I update my app code using SwiftUI 2 where there is no SceneDelegate

On my app I used to present programmatically an UIAlert with the following code:
debugPrint("didReceiveInvitationFromPeer")
let fieldAlert = UIAlertController(title: "Richiesta di conssessione",
message: "da parte di \(peerID.displayName)",
preferredStyle: .alert)
fieldAlert.addAction( UIAlertAction(title: "Rifiuta", style: .cancel) { (action) in
invitationHandler(false, nil)
} )
fieldAlert.addAction( UIAlertAction(title: "accetta", style: .default) { (action) in
invitationHandler(true, self.session)
delay(2) {
// do stuff
}
} )
if let scene = UIApplication.shared.connectedScenes.first,
let sd : SceneDelegate = (scene.delegate as? SceneDelegate) {
sd.window?.rootViewController?.present(fieldAlert, animated: true, completion: nil)
}
but now SceneDelegate not exists anymore, is there any solution to how can present the alert?
No need SceneDelegate, you can directly present by this
UIApplication.shared.windows.first?.rootViewController?.present(fieldAlert, animated: true, completion: nil

Alert not showing in the ImagePicker

I want to offer the option to use either the photo library or the camera, but my alert to select either photos or use a camera doesn't show up when I add the present actionSheet. The compiled application goes straight to photo album without giving me the option to select camera or photo library.
This is the log from my Xcode 11:
2020-06-15 23:05:22.931477-0400 MeMe[6298:3505455] Attempt to present
on which is waiting for a delayed presention of
to complete 2020-06-15
23:05:22.931653-0400 MeMe[6298:3505455] Attempt to present
on
which is waiting for a delayed presention of to complete error in connection_block_invoke_2:
Connection interrupted
This is my code:
import UIKit
class ViewController: UIViewController, UIImagePickerControllerDelegate,
UINavigationControllerDelegate {
// OUTLETS *******************************************
// Image
#IBOutlet weak var imageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
}
// ACTIONS *******************************************
// Button
#IBAction func pickImage(_ sender: Any) {
let imagePickerController = UIImagePickerController()
// set imagePickerController as delegate
imagePickerController.delegate = self
// provide actionSheet to display the camera and photo options
let actionSheet = UIAlertController(title: "Source", message: "Take a picture or select a photo", preferredStyle: .actionSheet)
// add camera action to imagePickerController
actionSheet.addAction(UIAlertAction(title: "Camera", style: .default, handler:{(action:UIAlertAction) in
// check if the camera is available
if UIImagePickerController.isSourceTypeAvailable(.camera) {
imagePickerController.sourceType = .camera
}
else {
print("Camera not available")
}
}))
self.present(imagePickerController, animated: true, completion: nil)
// add photos action to imagePickerController
actionSheet.addAction(UIAlertAction(title: "Photos", style: .default, handler:{(action:UIAlertAction) in imagePickerController.sourceType = .photoLibrary}))
self.present(imagePickerController, animated: true, completion: nil)
// cancel
actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(actionSheet, animated: true, completion: nil)
}
// assign image to imageView
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
let image = info[UIImagePickerController.InfoKey.originalImage] as! UIImage
imageView.image = image
picker.dismiss(animated: true, completion: nil)
}
// dismiss the image selector
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion: nil)
}
}
You're trying to present the actionSheet after presenting imagePickerController which is not possible. You need to present the imagePickerController in the UIAlertAction's handler block. Here's the code:
#IBAction func pickImage(_ sender: Any) {
let imagePickerController = UIImagePickerController()
// set imagePickerController as delegate
imagePickerController.delegate = self
// provide actionSheet to display the camera and photo options
let actionSheet = UIAlertController(title: "Source", message: "Take a picture or select a photo", preferredStyle: .actionSheet)
// add camera action to imagePickerController
actionSheet.addAction(UIAlertAction(title: "Camera", style: .default, handler:{(action:UIAlertAction) in
// check if the camera is available
if UIImagePickerController.isSourceTypeAvailable(.camera) {
imagePickerController.sourceType = .camera
}
else {
print("Camera not available")
}
self.present(imagePickerController, animated: true, completion: nil)
}))
// add photos action to imagePickerController
actionSheet.addAction(UIAlertAction(title: "Photos", style: .default, handler:{(action:UIAlertAction) in
imagePickerController.sourceType = .photoLibrary
self.present(imagePickerController, animated: true, completion: nil)
}))
// cancel
actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(actionSheet, animated: true, completion: nil)
}
#IBAction func pickImage(_ sender: Any) {
let imagePickerController = UIImagePickerController()
imagePickerController.delegate = self
let actionSheet = UIAlertController(title: "Source", message: "Take a picture or select a photo", preferredStyle: .actionSheet)
actionSheet.addAction(UIAlertAction(title: "Camera", style: .default, handler:{(action:UIAlertAction) in
if UIImagePickerController.isSourceTypeAvailable(.camera) {
imagePickerController.sourceType = .camera
self.present(imagePickerController, animated: true, completion: nil)
} else {
print("Camera not available")
}
}))
actionSheet.addAction(UIAlertAction(title: "Photos", style: .default, handler:{(action:UIAlertAction) in
imagePickerController.sourceType = .photoLibrary
self.present(imagePickerController, animated: true, completion: nil)
}))
// cancel
actionSheet.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
self.present(actionSheet, animated: true, completion: nil)
}

Can't Save UIImage on UIViewController

I've tried to save UIImage on UIViewcontroller named secondViewController, But didn't work.
Code snippet for saving image is attached.
#IBAction func takePhoto(_ sender: Any) {
cameraSession.stopRunning()
UIImageWriteToSavedPhotosAlbum(self.cameraImageView.image!, nil, nil, nil)
}
On SecondViewController, UIImage has been saved to cameraImageView.image,
But when check camera roll, nothing saved.
Does anyone know why?
Add Permission in your info.plist
"Privacy - Photo Library Additions Usage Description"
After permission granted try to save image.
#IBAction func takePhoto(_ sender: Any) {
cameraSession.stopRunning()
UIImageWriteToSavedPhotosAlbum(self.cameraImageView.image!, nil, nil, nil)
}
hey I added full code to capture image using UIImagePickerController and also added code to import a photo from photo gallery, hope it will helpful for you
import UIImagePickerControllerDelegateand create a variable to assign UIImagePickerController var imagePicker = UIImagePickerController()
and set imagePicker.delegate = self.
Create a action sheet to display options for 'Camera' and 'Photo library'.
On your button click action:
#IBAction func buttonOnClick(_ sender: UIButton)
{
self.btnEdit.setTitleColor(UIColor.white, for: .normal)
self.btnEdit.isUserInteractionEnabled = true
let alert = UIAlertController(title: "Choose Image", message: nil, preferredStyle: .actionSheet)
alert.addAction(UIAlertAction(title: "Camera", style: .default, handler: { _ in
self.openCamera()
}))
alert.addAction(UIAlertAction(title: "Gallery", style: .default, handler: { _ in
self.openGallary()
}))
alert.addAction(UIAlertAction.init(title: "Cancel", style: .cancel, handler: nil))
/*If you want work actionsheet on ipad
then you have to use popoverPresentationController to present the actionsheet,
otherwise app will crash on iPad */
switch UIDevice.current.userInterfaceIdiom {
case .pad:
alert.popoverPresentationController?.sourceView = sender
alert.popoverPresentationController?.sourceRect = sender.bounds
alert.popoverPresentationController?.permittedArrowDirections = .up
default:
break
}
self.present(alert, animated: true, completion: nil)
}
func openCamera() {
if(UIImagePickerController .isSourceTypeAvailable(UIImagePickerControllerSourceType.camera))
{
imagePicker.sourceType = UIImagePickerControllerSourceType.camera
imagePicker.allowsEditing = true
self.present(imagePicker, animated: true, completion: nil)
}
else
{
let alert = UIAlertController(title: "Warning", message: "You don't have camera", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
func openGallary() {
imagePicker.sourceType = UIImagePickerControllerSourceType.photoLibrary
imagePicker.allowsEditing = true
self.present(imagePicker, animated: true, completion: nil)
}

Understanding crashlytics UIKit errors

I am having trouble understanding the UIKit crash reports that I am receiving:
Is there a way of finding out what line of code caused this:
Crashed: com.apple.main-thread
0 UIKit 0x195694264 __56-[UIPresentationController runTransitionForCurrentState]_block_invoke + 444
1 UIKit 0x1955d0950 _runAfterCACommitDeferredBlocks + 292
2 UIKit 0x1955c29ec _cleanUpAfterCAFlushAndRunDeferredBlocks + 528
3 UIKit 0x195336648 _afterCACommitHandler + 132
4 CoreFoundation 0x18f1c09a8 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 32
5 CoreFoundation 0x18f1be630 __CFRunLoopDoObservers + 372
6 CoreFoundation 0x18f1bea7c __CFRunLoopRun + 956
7 CoreFoundation 0x18f0eeda4 CFRunLoopRunSpecific + 424
8 GraphicsServices 0x190b58074 GSEventRunModal + 100
9 UIKit 0x1953a9058 UIApplicationMain + 208
10 FlexConnect 0x1001b48c8 main (AppDelegate.swift:20)
11 libdyld.dylib 0x18e0fd59c start + 4
The error itself is:
Crashed: com.apple.main-thread
EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x0000000000000010
EDIT:
Based of the answer below, I'm wondering if it is advisable to use:
func topMostController() -> UIViewController {
var topController: UIViewController = UIApplication.sharedApplication().keyWindow!.rootViewController!
while (topController.presentedViewController != nil) {
topController = topController.presentedViewController!
}
return topController
}
and always call
let topVC = topMostController().dismiss(animated: true, completion: nil)
everywhere in my app where I currently have self.dismiss(animated: true, completion: nil)?
Is this a necessary check or how can I pin down where self.dismiss is having an issue?
Some sample dismissals:
#IBAction func returnToDash(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
let pending = UIAlertController(title: "\n\n\n\(title)", message: nil, preferredStyle: .alert)
displayActivityAlertWithCompletion2(ViewController: self, pending: pending){_ in
Helper_StatusCheck.doSync(_cleanSync: false){
Prefs.is_Syncing = false
DispatchQueue.main.async {
pending.dismiss(animated: true){
Toast(text: "Upload sync completed").show()
self.dismiss(animated: true, completion: nil)
}
}
}
}
where displayActivityAlertWithCompletion2 looks like:
public func displayActivityAlertWithCompletion2(ViewController: UIViewController, pending: UIAlertController, completionHandler: #escaping ()->())
{
//let pending = UIAlertController(title: "\n\n\n"+title, message: nil, preferredStyle: .alert)
//create an activity indicator
let indicator = UIActivityIndicatorView(frame: pending.view.bounds)
indicator.autoresizingMask = [.flexibleWidth, .flexibleHeight]
indicator.color = UIColor(rgba: Palette.loadingColour)
//add the activity indicator as a subview of the alert controller's view
pending.view.addSubview(indicator)
indicator.isUserInteractionEnabled = false
// required otherwise if there buttons in the UIAlertController you will not be able to press them
indicator.startAnimating()
ViewController.present(pending, animated: true, completion: completionHandler)
}
EDIT 2 :
Some sample popover methods in my app:
#IBAction func search(_ sender: UIButton) {
if let popView = UIStoryboard(name: "AssetCommon", bundle: nil).instantiateViewController(withIdentifier: "searchPop") as? searchPopVC {
popView.delegate = self
popView.modalPresentationStyle = .popover;
popView.popoverPresentationController?.delegate = self
popView.popoverPresentationController?.barButtonItem = searchButton
popView.popoverPresentationController?.permittedArrowDirections = .any
popView.preferredContentSize = CGSize(width: 300, height: 70)
self.present(popView, animated: true, completion: nil)
}
}
and search pop:
class searchPopVC: UIViewController
{
#IBOutlet weak var searchBar: UISearchBar!
weak var delegate: SearchPopDelegate?
override func viewDidLoad()
{
super.viewDidLoad()
searchBar.delegate = self;
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
#IBAction func performSearch(_ sender: UIButton)
{
let term = searchBar.text ?? "";
delegate?.performSearch(with: term)
self.dismiss(animated: true, completion: nil);
}
}
extension searchPopVC: UISearchBarDelegate
{
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
let term = searchBar.text ?? "";
delegate?.performSearch(with: term);
self.dismiss(animated: true, completion: nil)
}
}
You won't be able to find the line of code from this crash. However, this crash happens when the view controller you use to call dismiss(animated:completion:) is removed from the view hierarchy prior to the animation completing.
Depending on how your code is setup, you can try to request a view controller higher up to call the dismissal. Another solution could be to retain the view controller in a property until your for sure done with it.
Edit: 1/5/18
In response to the comments, here is an example of how a function can be made for view controllers that logs an event and dismisses.
extension UIViewController {
func dismissAndLog(animated: Bool, completion: (() -> ())? = nil) {
// Here are two examples of how your view controller can be identified.
// let id = title
let id = String(describing: type(of: self))
CLSLogv("Dismissed View Controller: %#", getVaList([id]))
dismiss(animated: animated, completion: completion)
}
}
I'm not entirely familiar with Crashlytics or their API, so if this logging is giving you issues, you can check out these links.
https://docs.fabric.io/apple/crashlytics/enhanced-reports.html#custom-logging-in-swift
How to use Crashlytics logging in Swift?
Also I don't know how they provide the data to you, so I can't explain to you the best way to parse it. However certainly the timestamps can be successfully used as a final solution. You can also try emailing their support asking for the best way to map these logs to the crash.
var topController: UIViewController = UIApplication.sharedApplication().keyWindow!.rootViewController!
This many exclamation marks should tell you that there is something not great about this code. Guard and check these assumptions because any one of them may blow up.
Other than that, what gives you some really weird errors, is if you do these kind of dismiss/pop/present actions as part of a defered block like you are doing in Helper_StatusCheck.doSync. There can be all kinds of changes to the navigation stack in between and your assumptions may not hold.

How do we create and dismiss an UIAlertController without user input? (Swift)

I've been looking up a lot of tutorials on UIAlertController. Thus far, the way I found was to activate a UIAlertController by linking it to a button or label and then call a IBAction.
I tried to replicate the code to automatically pop an alert when user enters the app (I wanted to ask the user if they want to go through the tutorial). However, I keep getting the error:
Warning: Attempt to present UIAlertController on MainViewController whose view is not in the window hierarchy!
Then I tried to add the UIAlertController to the MainViewController via addChildViewController and addSubview. However, I get the error:
Application tried to present modally an active controller
I figured that I cannot use the presentViewController function and commented it out.
The UIAlertController is displayed BUT when I tried to click on the cancel or the never button, this error occurs.
Trying to dismiss UIAlertController with unknown presenter.
I am really stumped. Can someone share what I am doing wrong? Thank you so much. Here is the code.
func displayTutorial() {
alertController = UIAlertController(title: NSLocalizedString("tutorialAlert", comment: ""), message: NSLocalizedString("tutorialMsg", comment: ""), preferredStyle: .ActionSheet)
self.addChildViewController(alertController)
self.view.addSubview(alertController.view)
alertController.didMoveToParentViewController(self)
alertController.view.frame.origin.x = self.view.frame.midX
alertController.view.frame.origin.y = self.view.frame.midY
//alertController.popoverPresentationController?.sourceView = self.view*/
let OkAction = UIAlertAction(title: NSLocalizedString("yesh", comment: ""), style: .Destructive) { (action) in
}
alertController.addAction(OkAction)
let cancelAction = UIAlertAction(title: NSLocalizedString("notNow", comment: ""), style: .Destructive) { (action) in
//println(action)
self.tutorial = 1
self.presentedViewController?.dismissViewControllerAnimated(true, completion: nil)
}
alertController.addAction(cancelAction)
let neverAction = UIAlertAction(title: NSLocalizedString("never", comment: ""), style: .Cancel) { (action) in
self.tutorial = 1
}
alertController.addAction(neverAction)
//self.presentViewController(alertController, animated: false) {}
}
I found the solution. Apparently, I cannot call the UIAlertController from the func viewDidLoad. I must call the function from viewDidAppear. So my code now is
override func viewDidAppear(animated: Bool) {
if tutorial == 0 {
displayTutorial(self.view)
}
}
func displayTutorial(sender:AnyObject) {
let alertController = UIAlertController(title: NSLocalizedString("tutorialAlert", comment: ""), message: NSLocalizedString("tutorialMsg", comment: ""), preferredStyle: .ActionSheet)
let OkAction = UIAlertAction(title: NSLocalizedString("yesh", comment: ""), style: .Destructive) { (action) in
}
alertController.addAction(OkAction)
let cancelAction = UIAlertAction(title: NSLocalizedString("notNow", comment: ""), style: .Default) { (action) in
//println(action)
self.tutorial = 1
self.presentedViewController?.dismissViewControllerAnimated(true, completion: nil)
}
alertController.addAction(cancelAction)
let neverAction = UIAlertAction(title: NSLocalizedString("never", comment: ""), style: .Cancel) { (action) in
self.tutorial = 1
}
alertController.addAction(neverAction)
self.presentViewController(alertController, animated: true, completion: nil)
if let pop = alertController.popoverPresentationController {
let v = sender as UIView
pop.sourceView = view
pop.sourceRect = v.bounds
}
}
Thanks to this posting: Warning: Attempt to present * on * whose view is not in the window hierarchy - swift
Below UIAlertController with extension would help you show alert with dynamic number of buttons with completion handler for selected index
extension UIViewController {
func displayAlertWith(message:String) {
displayAlertWith(message: message, buttons: ["Dismiss"]) { (index) in
}
}
func displayAlertWith(message:String, buttons:[String], completion:((_ index:Int) -> Void)!) -> Void {
displayAlertWithTitleFromVC(vc: self, title: Bundle.main.infoDictionary!["CFBundleDisplayName"] as! String, andMessage: message, buttons: buttons, completion: completion)
}
func displayAlertWithTitleFromVC(vc:UIViewController, title:String, andMessage message:String, buttons:[String], completion:((_ index:Int) -> Void)!) -> Void {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
for index in 0..<buttons.count {
let action = UIAlertAction(title: buttons[index], style: .default, handler: {
(alert: UIAlertAction!) in
if(completion != nil){
completion(index)
}
})
alertController.addAction(action)
}
DispatchQueue.main.async {
vc.present(alertController, animated: true, completion: nil)
}
}
}
If you need to auto dismiss the alert you can call dismiss on presented view controller after some delay.
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1) {
vc.dismiss(animated: true, completion: nil)
}
Hope this might help you.