Swift Geocoder won't geocode address - swift

When I pass an address string into CLGeocoder.geocodeAddressString(), the function never executes. When debugging, it never enters geocodeAddressString() and instead skips over. location1 does contain a valid address.
func routeToLocation(location1: String, location2: String) {
var location1Parsed = CLLocation()
var location2Parsed = CLLocation()
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(location1, completionHandler: {(placemarks, error) -> Void in
if ((error) != nil) {
print("Error")
}
if let placemark = placemarks?.first{
let coordinates: CLLocationCoordinate2D = placemark.location!.coordinate
location1Parsed = CLLocation(latitude: coordinates.latitude, longitude: coordinates.longitude)
print(location1Parsed.coordinate.latitude)
print(location1Parsed.coordinate.longitude)
}
})
...
}
Does anyone know why this might be happening?
Thanks

Related

SwiftUI async data receive from #escaping closure

I am having trouble with working over data I receive from server.
Each time server sends data it is new coordinates for each user. I am looping over each incoming data, and I want to send the data in completion to receive them on other end. And update model class with them. At the moment I have two users in server. And sometimes the closure passes data two times, but sometimes just one. And interesting thing is that class properties are not updated, at least I dont see them on UI.
This is function I call when data is received. Response is just string I split to get user data.
func updateUserAdrress(response: String, completion: #escaping (Int, Double, Double, String) -> Void){
var data = response.components(separatedBy: "\n")
data.removeLast()
data.forEach { part in
let components = part.components(separatedBy: ",")
let userID = components[0].components(separatedBy: " ")
let id = Int(userID[1])
let latitude = Double(components[1])!
let longitude = Double(components[2])!
let location = CLLocation(latitude: latitude, longitude: longitude)
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
if error == nil {
let placemark = placemarks?[0]
if let thoroughfare = placemark?.thoroughfare, let subThoroughfare = placemark?.subThoroughfare {
let collectedAddress = thoroughfare + " " + subThoroughfare
DispatchQueue.main.async {
completion(id!, latitude, longitude, collectedAddress)
}
}
} else {
print("Could not get address \(error!.localizedDescription)")
}
}
}
}
In this function I try to invoke the changes on objects. As the incoming data from server is different the first time I have splited the functionality, so the correct block of code would be called.
func collectUsers(_ response: String){
if users.count != 0{
updateUserAdrress(response: response) { id, latitude, longitude, address in
if let index = self.users.firstIndex(where: { $0.id == id }){
let user = self.users[index]
user.latitude = latitude
user.longitude = longitude
user.address = address
}
}
}else{
var userData = response.components(separatedBy: ";")
userData.removeLast()
let users = userData.compactMap { userString -> User? in
let userProperties = userString.components(separatedBy: ",")
var idPart = userProperties[0].components(separatedBy: " ")
if idPart.count == 2{
idPart.removeFirst()
}
guard userProperties.count == 5 else { return nil }
guard let id = Int(idPart[0]),
let latitude = Double(userProperties[3]),
let longitude = Double(userProperties[4]) else { return nil }
let collectedUser = User(id: id, name: userProperties[1], image: userProperties[2], latitude: latitude, longitude: longitude)
return collectedUser
}
DispatchQueue.main.async {
self.users = users
}
}
}
As I also need user address when app starts in model I have made simular function to call in init so it would get address for user. That seems to be working fine. But for more context I will add the model to.
class User: Identifiable {
var id: Int
let name: String
let image: String
var latitude: Double
var longitude: Double
var address: String = ""
init(id: Int, name: String, image: String, latitude: Double, longitude: Double){
self.id = id
self.name = name
self.image = image
self.latitude = latitude
self.longitude = longitude
getAddress(latitude: latitude, longitude: longitude) { address in
self.address = address
}
}
func getAddress(latitude: Double, longitude: Double, completion: #escaping (String) -> Void){
let location = CLLocation(latitude: latitude, longitude: longitude)
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(location) { (placemarks, error) in
if error == nil {
let placemark = placemarks?[0]
if let thoroughfare = placemark?.thoroughfare, let subThoroughfare = placemark?.subThoroughfare {
let collectedAddress = thoroughfare + " " + subThoroughfare
completion(collectedAddress)
}
} else {
print("Could not get address \(error!.localizedDescription)")
}
}
}
}
And one interesting thing. That when closure receives two times data and I assign them to class, there are no changes on UI.
I made this project on Xcode 13.4.1 because on Xcode 14 there is a bug on MapAnnotations throwing purple warnings on view changes.

How to properly wait until function has finished doing in swift?

I have now tried lots of things, but none of them seem to work.
I have a for loop which parses some data and converts coordinates into ZIP string:
for i in 0 ... results.count - 1
{
result = results[i]
self.coordinateToString(lat: result.lat, long: result.long, completion: { (place) in
someCell.label.text = place
})
}
func coordinateToString(lat: Double, long: Double, completion: #escaping (String) -> ()) {
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: lat, longitude: long)
var ret = ""
geoCoder.reverseGeocodeLocation(location, completionHandler:
{
placemarks, error -> Void in
guard let placeMark = placemarks?.first else { return }
if let zip = placeMark.postalCode, let town = placeMark.subAdministrativeArea
{
let toAppend = "\(zip)" + " \(town)"
ret = toAppend
}
})
DispatchQueue.main.async {
completion(ret)
}
}
However I never manage to show the correct place in the cell, it always shows empty space because it somehow doesn't wait for the completion handler to finish converting. What am I doing wrong here?
This happens because reverseGeocodeLocation returns right away and its completion handler runs afterwards. This means that ret value may be empty when it gets put on the main queue. You should dispatch to main from within the callback, like so:
func coordinateToString(lat: Double, long: Double, completion: #escaping (String) -> ()) {
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: lat, longitude: long)
var ret = ""
geoCoder.reverseGeocodeLocation(location, completionHandler:
{
placemarks, error -> Void in
guard let placeMark = placemarks?.first else { return }
if let zip = placeMark.postalCode, let town = placeMark.subAdministrativeArea
{
let toAppend = "\(zip)" + " \(town)"
ret = toAppend
DispatchQueue.main.async {
completion(ret)
}
}
})
Of course, given this scenario, you need to handle error cases accordingly. Better yet, use defer, that way completion gets called regardless of what happens:
func coordinateToString(lat: Double, long: Double, completion: #escaping (String) -> ()) {
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: lat, longitude: long)
var ret = ""
geoCoder.reverseGeocodeLocation(location, completionHandler:
{
defer {
DispatchQueue.main.async {
completion(ret)
}
}
placemarks, error -> Void in
guard let placeMark = placemarks?.first else { return }
if let zip = placeMark.postalCode, let town = placeMark.subAdministrativeArea
{
let toAppend = "\(zip)" + " \(town)"
ret = toAppend
}
})

Get Data From Async Completion Handler

Trying to get name of a city, while having latitude and longitude.
Inside a model class Location, I'm using reverseGeocodeLocation(location: , completionHandler: ) func that comes with CLGeocoder (part of CoreLocation).
func getLocationName() {
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: currentLatitude, longitude: currentLongitude)
geoCoder.reverseGeocodeLocation(location, completionHandler: { placemarks, error in
guard let addressDict = placemarks?[0].addressDictionary else {
return
}
if let city = addressDict["City"] as? String {
self.currentCity = city
print(city)
}
if let zip = addressDict["ZIP"] as? String {
print(zip)
}
if let country = addressDict["Country"] as? String {
print(country)
}
})
}
However, in ViewController, after running the getLocationName(), the location.currentCity is nil, since the completion handler is async, and wasn't finished yet.
How can I make sure that the completion handler is finished running so I can access location.currentCity ?
Pass a closure as a function parameter in your getLocationName which
you can call inside the reverseGeocodeLocation closure.
func updateLocation(currentCity : String) -> Void
{
print(currentCity)
}
func getLocationName(callback : #escaping (String) -> Void)
{
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: currentLatitude, longitude: currentLongitude)
geoCoder.reverseGeocodeLocation(location, completionHandler: { placemarks, error in
guard let addressDict = placemarks?[0].addressDictionary else {
return
}
if let city = addressDict["City"] as? String
{
self.currentCity = city
callback(city)
print(city)
}
if let zip = addressDict["ZIP"] as? String {
print(zip)
}
if let country = addressDict["Country"] as? String {
print(country)
}
})
}
In your ViewController...
getLocationName(callback: updateLocation)
I would create a function where location.currentCity is used, and call this function from the completion handler
So if your code looks like:
func foo() {
var location
getLocationName()
print(location.currentcity) // nil
}
change it to:
func foo() {
var location
getLocationName()
}
func bar() {
print(location.currentcity) // someplace
}
and call bar() from your completion handler

A method without parameters is calling for an argument

I have a class named Location that has several methods in it that do not have any parameters.
However, when I try to create a variable with the result of the method, it wants an argument. Why is that?
Location class:
let locationManager = CLLocationManager()
public class Location {
public func coordinate() -> (latitude: Float?, longitude: Float?) {
let latitude = Float((locationManager.location?.coordinate.latitude)!)
let longitude = Float((locationManager.location?.coordinate.longitude)!)
return (latitude: latitude, longitude: longitude)
}
public func getCity() -> String {
var returnCity: String = "N/A"
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: (locationManager.location?.coordinate.latitude)!, longitude: (locationManager.location?.coordinate.longitude)!)
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
// Place details
var placeMark: CLPlacemark!
placeMark = placemarks?[0]
// City
if let city = placeMark.addressDictionary!["City"] as? String {
returnCity = city
}
})
return returnCity
}
public func getCountry() -> String {
var returnCountry: String = "N/A"
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: (locationManager.location?.coordinate.latitude)!, longitude: (locationManager.location?.coordinate.longitude)!)
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
// Place details
var placeMark: CLPlacemark!
placeMark = placemarks?[0]
// City
if let country = placeMark.addressDictionary!["Country"] as? String {
returnCountry = country
}
})
return returnCountry
}
public func getZip() -> Int {
var returnZip: Int = 0
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: (locationManager.location?.coordinate.latitude)!, longitude: (locationManager.location?.coordinate.longitude)!)
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
// Place details
var placeMark: CLPlacemark!
placeMark = placemarks?[0]
// City
if let zip = placeMark.addressDictionary!["ZIP"] as? Int {
returnZip = zip
}
})
return returnZip
}
public func getLocationName() -> String {
var returnName: String = "N/A"
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: (locationManager.location?.coordinate.latitude)!, longitude: (locationManager.location?.coordinate.longitude)!)
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
// Place details
var placeMark: CLPlacemark!
placeMark = placemarks?[0]
// City
if let locationName = placeMark.addressDictionary!["Name"] as? String {
returnName = locationName
}
})
return returnName
}
public func getStreetAddress() -> String {
var returnAddress: String = "N/A"
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: (locationManager.location?.coordinate.latitude)!, longitude: (locationManager.location?.coordinate.longitude)!)
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
// Place details
var placeMark: CLPlacemark!
placeMark = placemarks?[0]
// City
if let street = placeMark.addressDictionary!["Thoroughfare"] as? String {
returnAddress = street
}
})
return returnAddress
}
}
Trying to create a variable:
let city = Location.getCity()
Here are some screen shots of what I get:
These methods are not class methods, they are instance methods. You must call them on an instance of the Location class, not on the class itself. Evidently, Swift can call instance methods similarly to Python: the method is a function owned by the class, and its argument is an instance of the class. But you should not call instance methods this way.
The best way to solve this problem is to construct a Location object and then call the method on it:
let city: Location = Location().getCity()
Because you're trying to call it as a class function. You should be creating an instance of Location and calling the function on that. Note also that it returns String Where your code is telling the compiler you're expecting it to return a Location.

Swift Reverse Geocoding using the Data

I am successfully getting the current address details based on my location. It printlns perfectly. What is throwing me is how I extract the data from this call. I have tried passing, say the ZIP/Postcode, as local and even global variables but with no joy. The data only seems to exist within this call. How can I use it elsewhere?
// Get Address Information
let geoCoder = CLGeocoder()
let newLocation = CLLocation(latitude: valueLatitude, longitude: valueLongitude)
geoCoder.reverseGeocodeLocation(newLocation, completionHandler: {(placemarks: [AnyObject]!, error: NSError!) in
if error != nil {
println("Geocode failed with error: \(error.localizedDescription)")
}
if placemarks.count > 0 {
let placemark = placemarks[0] as! CLPlacemark
let addressDictionary = placemark.addressDictionary
let address = addressDictionary[kABPersonAddressStreetKey] as! NSString
let city = addressDictionary[kABPersonAddressCityKey] as! NSString
let state = addressDictionary[kABPersonAddressStateKey] as! NSString
let postcode = addressDictionary[kABPersonAddressZIPKey] as! NSString
let country = addressDictionary[kABPersonAddressCountryKey] as! NSString
println("\(address) \(city) \(state) \(postcode) \(country)") }
})
Your problem is most likely due to the fact that reverseGeocodeLocation is an asynchronous request made to Apple servers.
What needs to happen is:
You call reverseGeocodeLocation
reverseGeocodeLocation finishes, starts its completion which calls a method passing the placemark you just recovered.
In order to do that:
#IBAction func btnInsertClicked(sender: AnyObject) {
var locationRecord: LocationRecord = LocationRecord()
// Get Address Information
let geoCoder = CLGeocoder()
let newLocation = CLLocation(latitude: valueLatitude, longitude: valueLongitude)
geoCoder.reverseGeocodeLocation(newLocation, completionHandler:
{(placemarks: [AnyObject]!, error: NSError!) in
if error != nil {
println("Geocode failed with error: (error.localizedDescription)")
}
if placemarks.count > 0 {
let myPlacemark = placemarks[0] as! CLPlacemark
// Here call the method that uses myPlacemark
self.myAwesomeMethod(placemarks[0] as! CLPlacemark)
} else {
println("No placemark")
}
})
}
Where you need to use it in your code:
func myAwesomeMethod(placemark: CLPlacemark) {
// Do stuff with placemark
}
I won't have my mac until tonight but if that doesn't work, leave a comment and we will work this out