0
votes

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

}
2

2 Answers

0
votes

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

0
votes

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.