I am receiving a warning: "Warning: Attempt to present on whose view is not in the window hierarchy!"
I think I have a logic problem related to the presentation of the new view controller via a segue. I am attempting to geocode a location and assign the coordinates to variables in a struct (along with a couple of other variables) that I then send to a new controller which displays a pin on a mapView.
Via breakpoints and print statements, it appears that my prepareForSegue function is called twice, once before the geolocation actually occurs and after the next view controller is called, and once after the geolocation has completed... but I believe it can't display the geocoded location because the view controller already exists... I always end up in the middle of the ocean somewhere.
I have tried moving my async function (performUIUpdatesOnMain) around and moving my map and annotation display functionality into viewDidAppear in the second view controller, but these solutions don't work. What am I doing wrong?
Here are the relevant parts of each view controller:
First view controller:
@IBAction func findLocationPressed(_ sender: Any) {
// Show alert if locationTextField or websiteTextField are empty
if self.locationTextField.text == "" || self.websiteTextField.text == "" {
let alertController = UIAlertController()
let alert = UIAlertAction(title: "You didn't enter a location or website", style: .cancel, handler: nil)
alertController.addAction(alert)
present(alertController, animated: true, completion: nil)
} else {
// Populate locationData struct with locationText and mediaURL
locationData.locationText = self.locationTextField.text!
locationData.mediaURL = self.websiteTextField.text!
// Get the location
getLocation(completionHandler: { (success, error) in
//AlertView.activityIndicator
//performUIUpdatesOnMain {
// If geocoding successful...
if success {
// Present the ConfirmLocationViewController
self.performSegue(withIdentifier: "FinishSegue", sender: self)
/*let controller = self.storyboard?.instantiateViewController(withIdentifier: "ConfirmLocationViewController") as! ConfirmLocationViewController
self.present(controller, animated: true, completion: nil) */
} else {
let alertController = UIAlertController()
let alert = UIAlertAction(title: "Couldn't find that location", style: .cancel, handler: nil)
alertController.addAction(alert)
self.present(alertController, animated: true, completion: nil)
//}
}
})
}
}
func getLocation(completionHandler: @escaping (_ success: Bool, _ error: NSError?) -> Void) {
// Create an instance of the geocoder
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(locationTextField.text!) { (placemark, error) in
performUIUpdatesOnMain {
// Check for an error when retrieving the coordinates
if error != nil {
let userInfo = [NSLocalizedDescriptionKey: "There was an error attempting to retrieve your coordinates"]
completionHandler(false, NSError(domain: "getLocation", code: 0, userInfo: userInfo))
} else {
// GUARD: Confirm placemark exists
guard let placemark = placemark else {
let userInfo = [NSLocalizedDescriptionKey: "There was an error with your placemark"]
completionHandler(false, NSError(domain: "getLocation", code: 1, userInfo: userInfo))
return
}
// GUARD: Confirm latitude exists in placemark
guard let latitude = placemark[0].location?.coordinate.latitude else {
let userInfo = [NSLocalizedDescriptionKey: "There was an error with the latitude"]
completionHandler(false, NSError(domain: "getLocation", code: 2, userInfo: userInfo))
return
}
// GUARD: Confirm longitude exists in placemark
guard let longitude = placemark[0].location?.coordinate.longitude else {
let userInfo = [NSLocalizedDescriptionKey: "There was an error with the longitude"]
completionHandler(false, NSError(domain: "getLocation", code: 3, userInfo: userInfo))
return
}
// Populate locationData with latitude and longitude
self.locationData.latitude = latitude
self.locationData.longitude = longitude
completionHandler(true, nil)
print("getLocation was successful. \n Latitude=\(latitude) \n Longitude=\(longitude)")
}
}
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "FinishSegue" {
let controller = segue.destination as! ConfirmLocationViewController
controller.locationData = self.locationData
print("This is the locationData being sent via prepareForSegue: \(locationData)")
}
}
Second view controller:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
print("viewWillAppear in ConfirmLocationViewController has been called")
// Set the coordinates
let coordinates = CLLocationCoordinate2D(latitude: locationData.latitude, longitude: locationData.longitude)
print(coordinates)
// Set the map region
let region = MKCoordinateRegionMake(coordinates, MKCoordinateSpan(latitudeDelta: 1, longitudeDelta: 1))
self.mapView.setRegion(region, animated: true)
self.mapView.delegate = self
// Set the annotation
let title = "\((User.shared.firstName) + " " + (User.shared.lastName))"
let subtitle = locationData.mediaURL
annotation.coordinate = coordinates
annotation.title = title
annotation.subtitle = subtitle
// Add the annotation
mapView.addAnnotation(self.annotation)
self.mapView.addAnnotation(self.annotation)
print("the current map region is: \(region)")
/*performUIUpdatesOnMain {
} */
}
The struct I am passing, called locationData contains four variables: latitude, longitude, and two strings.
Thanks.