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)
}
}
}
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)
}
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With