9

I'm trying to display a UIAlertController when a button is clicked (button click runs certain code, and depending on said outcome - the Alert shows up). The initial ViewController is the default one, and I've created a second one (ConsoleViewController). The user logs in, and upon successful log in, segues to the next view (ConsoleViewController), which displays data (which is in the viewDidLoad() section of the ConsoleViewController). Once the user clicks "Check In", the app captures the GPS location of the device, the current date/time, and opens the camera to take a (selfie) picture. Upon selection of "Use Photo" in the camera (havent coded that feature yet), it sends all 3 parameters to an API handler.

The second button opens a date picker and the user selects a date and time. Upon tapping the "Submit" button, a label.text is updated with the selected date (from the date picker), and an Alert should pop up stating the date was successfully saved based on the returnString from the API handler.

The issue I'm having, is that I want an Alert popup to display saying either "Success" or "Failed" depending on if the data was successfully sent or not (based on the returnString of the API handler). I keep getting the error Warning: Attempt to present <UIAlertController: 0x7fd03961b0a0> on <appName.ViewController: 0x7fd039538640> whose view is not in the window hierarchy!. I have tried adding the alert view to the main thread, I've tried changing the way the segue is presented (push, modal, etc), and just about everything else I could find here on StackOverFlow (as well as searching Google), and no solution seems to work for me. I created alerts based on incorrect login credentials (on the ViewController), and that pop up works correctly.

Below is my code... Don't mind my random print lines...it helps me keep track of where I am lol.

Note: I have added the corresponding info.plist items to show the correct pop ups from iOS. These as well, tell me they aren't in the view hierarchy

    func consoleAlertPopup(title: String, message: String) {

    let alertController = UIAlertController(title: title, message: message,     preferredStyle: UIAlertControllerStyle.alert)
        UIApplication.shared.keyWindow?.rootViewController?.present(alertController,     animated: true, completion: nil)
    alertController.addAction(UIAlertAction(title: "Try Again", style:     UIAlertActionStyle.default, handler:  nil))
}

ConsoleViewController:

import UIKit
import CoreLocation
import MobileCoreServices

class ConsoleViewController: UIViewController, CLLocationManagerDelegate {

var alertView: UIAlertController?

// IB Outlets \\
@IBOutlet var DisplayUserName: UILabel!
@IBOutlet var LastCheckInLabel: UILabel!
@IBOutlet var NextCourtDateLabel: UILabel!
@IBOutlet weak var CourtDateButton: UIButton!

@IBOutlet weak var courtDatePicker: UIDatePicker!

//Global Variables & UI Elements
var checkInImg: UIImage!
var userNameString: String!
var newDisplayDate: String?
var updatedCourtLabel: String?
let formatter = DateFormatter()
let displayFormatter = DateFormatter()
var locationManager: CLLocationManager!

@IBAction func clickCheckIn(_ sender: UIButton) {

    sendPicture()
    //Camera Pop Up

}

@IBAction func clickCourtDate() {

    courtPickerAction(Any.self)

}

@IBAction func courtPickerAction(_ sender: Any) {


   DatePickerDialog().show("Select Next Court Date", doneButtonTitle: "Submit", cancelButtonTitle: "Cancel", datePickerMode: .dateAndTime) {
        (courtDateTime) -> Void in

    if courtDateTime == nil {
        //Do nothing
    } else {
        self.formatter.dateFormat = "yyyy-MM-dd HH:mm"
        self.newDisplayDate = self.formatter.string(from: (courtDateTime)!)


    //print("Date after format: \(courtDateTime)")
    print("Date and time: \(self.newDisplayDate) after sendDefendantData func")


    // Submit Button - Date Picker \\
    if (DatePickerDialog().doneButton != nil) {

        self.sendDefendantData()


        print("Send Defendant Data from Submit")
        print("After sendDefData: \(self.newDisplayDate)")

        self.displayFormatter.dateStyle = DateFormatter.Style.full
        self.displayFormatter.timeStyle = DateFormatter.Style.short
        self.NextCourtDateLabel.text = self.displayFormatter.string(from: courtDateTime!)

            }

        }
    }
}


override func viewDidLoad() {
    super.viewDidLoad()


    print("Console View Did Load")
    self.hideKeyboardWhenTappedAround()

    DisplayUserName.text! = userNameString
    // For location allowance from user
    // I've placed this code here (instead of in a function) so the alert
    // pop up will show and allows accessing location. "not in hierarchy"
    // elsewise.
    self.locationManager = CLLocationManager()
    self.locationManager.delegate = self
    self.locationManager.requestWhenInUseAuthorization()

    // Format Display Date & Times
    self.displayFormatter.dateStyle = DateFormatter.Style.full
    self.displayFormatter.timeStyle = DateFormatter.Style.long


    // Retrieve Defendant Data From API Handler
    getDefendantData()





    // Do any additional setup after loading the view.
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}




func presentAlert(_ message: String) {

    self.alertView = UIAlertController(title: "Title", message: "Message", preferredStyle: .alert)
    alertView?.addAction(UIAlertAction(title: "OK", style: .cancel) { _ in })

    ViewController().present(alertView!, animated: true, completion: nil)

}


func consoleAlertPopup(title: String, message: String) {

    let alertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertControllerStyle.alert)
    UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
    alertController.addAction(UIAlertAction(title: "Try Again", style: UIAlertActionStyle.default, handler:  nil))
}

func getDefendantData() {...}
func sendDefendantData() {...}
func sendPicture() {....}

ViewController:

    import UIKit


// Hide Keyboard \\
extension UIViewController {
    func hideKeyboardWhenTappedAround() {
        let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
        view.addGestureRecognizer(tap)
    }

    func dismissKeyboard() {
        view.endEditing(true)
    }
}



class ViewController: UIViewController {

    // Send User Login to Console Screen \\
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        if (segue.identifier == "toConsoleScreen") {

            let secondViewController = segue.destination as! ConsoleViewController

            secondViewController.userNameString = UserNameField.text!

            print("PrepareSegue")
        }

    }

    @IBAction func UserNameEditBegan() {
        UserNameField.text = nil
    }
    @IBAction func PasswordEditBegan() {
        PasswordField.text = nil
    }

    @IBOutlet weak var UserNameField: UITextField!
    @IBOutlet weak var PasswordField: UITextField!

    func successfulLogin(Username: String) {


        print("Inside Function")

        print(Username)
        print("Inside Successful Login")

        // Show next view - Add to Main Queue\\
        OperationQueue.main.addOperation{

            //print("Before dismissal")
            // self.dismiss(animated: true, completion: nil)
            //print("After dismissal")
            self.performSegue(withIdentifier: "toConsoleScreen", sender: self)
            print("After segue")

        }
    }



    override func viewDidLoad() {
        super.viewDidLoad()

        self.hideKeyboardWhenTappedAround()



        // Do any additional setup after loading the view, typically from a nib.


    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @IBAction func loginButton() {

        login(Username: UserNameField.text!, Password: PasswordField.text!) { username in
            self.successfulLogin(Username: username)
    }



}



    }
szady
  • 125
  • 1
  • 1
  • 9
  • Have you carefully reviewed [these search results](http://stackoverflow.com/search?q=%5Bswift%5D+Attempt+to+present+whose+view+is+not+in+the+window+hierarchy+uialertcontroller)? – rmaddy Mar 06 '17 at 23:50
  • Make sure you set modalPresentationStyle = .overCurrentContext on your UIAlertViewController. – onnoweb Mar 07 '17 at 01:03
  • Can you clean up your code by 1) remove unnecessary spaces 2) comment on the line that shows the error 3) keep the question as short as possible? So that people will have time to read it through? – Bright Mar 07 '17 at 03:38
  • Why you need to present the alert in `ViewController` but not just simply `self`? – chengsam Mar 07 '17 at 03:55

2 Answers2

28

try to present your UIAlertController in a DispatchQueue

DispatchQueue.main.async {

    let alert = UIAlertController(title: "Alert!", message: nil, preferredStyle: .alert)

    let cancelAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)

    alert.addAction(cancelAction)
    self.present(alert, animated: true, completion: nil)   
}
HIEPING
  • 281
  • 3
  • 3
2

This line:

ViewController().present(alertView!, animated: true, completion: nil)

creates a new instance of ViewController and calls the present method on it. That won't work. You need to call it from a view controller that is itself presented. It looks like that code is inside ConsoleViewController, maybe you can just use self there.

Dave Weston
  • 6,527
  • 1
  • 29
  • 44
  • Thank you! I'm actually quite mad at myself for not catching that... Please ignore my ignorance... – szady Mar 07 '17 at 23:19
  • You're welcome! And don't worry about it. We've all been there. – Dave Weston Mar 07 '17 at 23:20
  • One last thing... The iOS notification for allowing use of GPS/camera I would like to be display once the user clicks on a button. I put the `self.locationManager.requestWhenInUseAuthorization()` in the IB action for the button click, but it says `"Warning: Attempt to present on while a presentation is in progress!` Any insight? – szady Mar 07 '17 at 23:44
  • 2
    It sounds like there are two different view controllers being presented at the same time. You might want to move that call to `requestWhenInUseAuthorization` to a later life cycle method, like `viewDidAppear`. – Dave Weston Mar 07 '17 at 23:55