If statement is not changing global variable swift - ios

I am trying to set up a function to get my current location in app delegate but when I print(city) at the bottom it returns the original initialized value in the global variable which is "hello", even though I updated the value under the CLGeocoder.
AppDelegate:
import UIKit
import CoreData
import CoreLocation
let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate
var country = "hello"
var city = "hello"
func setupLocationManager(){
let locationManager = CLLocationManager()
locationManager.requestAlwaysAuthorization()
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.startUpdatingLocation()
}
// Below method will provide you current location.
func getLocation() -> [String]{
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
manager.requestAlwaysAuthorization()
manager.startUpdatingLocation()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestAlwaysAuthorization()
manager.startUpdatingLocation()
let selflocation = manager.location
let latitude: Double = selflocation!.coordinate.latitude
let longitude: Double = selflocation!.coordinate.longitude
print("current latitude :: \(latitude)")
print("current longitude :: \(longitude)")
let location = CLLocation(latitude: latitude, longitude: longitude) //changed!!!
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
print(location)
if error != nil {
print("Reverse geocoder failed with error" + (error?.localizedDescription)!)
}
let pm = placemarks![0]
let speed = (selflocation?.speed)!
city = pm.addressDictionary!["City"]! as! String
country = pm.addressDictionary!["Country"]! as! String
if (placemarks?.count)! > 0 {
}
else {
print("Problem with the data received from geocoder")
}
})
print(city)
return [city as! String, country as! String]
}

This is because the geocoding is done asynchronously, so the print(city) is being executed before the geocoding is completed. So I suggest you do this.
func getLocation(completion: #escaping (Array<String>)->()){
let manager = CLLocationManager()
manager.desiredAccuracy = kCLLocationAccuracyBest
manager.requestAlwaysAuthorization()
manager.startUpdatingLocation()
let selflocation = manager.location
let latitude: Double = selflocation!.coordinate.latitude
let longitude: Double = selflocation!.coordinate.longitude
let location = CLLocation(latitude: latitude, longitude: longitude)
CLGeocoder().reverseGeocodeLocation(location, completionHandler: {(placemarks, error) -> Void in
if let error = error {
print(error.localizedDescription)
return
}
if let placemark = placemarks?.first {
if let country = placemark.country, let city = placemark.locality {
completion([city, country])
return
} else {
print("country or city was nil.")
}
} else {
print("Problem with the data received from geocoder")
}
})
}
So instead of calling getLocation() call
getLocation { (location) in
print(location)
}

The problem here is you are getting the value before a new location value is assigned to it. You have to wait a little bit to get the updated value.

reverseGeocodeLocation works asynchronously, so the print statement is actually happening before it finishes. If you have any logic that depends on the results, you'll probably need to put it inside the completion handler closure.

Like the other answer says, reverseGeocodeLocation works asynchronously, so you may want to move the print(city) inside the closure, such as after
else {
print("Problem with the data received from geocoder")
}

Related

Use variables inside a completion handler somewhere else

I'm trying to convert address to coordinates in order to create a Firestorm GeoPoint object.
I currently have this code:
func getCoords(from address: String, locationCompletionHandler: #escaping (CLLocationCoordinate2D?, Error?) -> Void) {
let geoCoder = CLGeocoder()
geoCoder.geocodeAddressString(address) { (placemarks, error) in
guard
let placemarks = placemarks,
let coordinate = placemarks.first?.location?.coordinate
else {
locationCompletionHandler(nil, error)
return
}
locationCompletionHandler(coordinate, nil)
}
}
func addressToGeoPoint(from address: String) -> GeoPoint {
var latitude:Double = 0
var longitude:Double = 0
getCoords(from: address) { coordinate, error in
if let coordinate = coordinate {
latitude = coordinate.latitude
longitude = coordinate.longitude
}
else {
print("Can't get coords: \(String(describing: error?.localizedDescription))")
}
}
return GeoPoint(latitude: latitude, longitude: longitude)
}
The problem is that when the GeoPoint object is being initialized, the latitude and longitude variables are still 0 because the completion handler hasn't finished yet.
The function addressToGeoPoint must return a GeoPoint.
What can I do in order for this to work?
Thanks!

Iterate through JSON array and add coordinates to the map

I'm using an API to get latitude and longitude coordinates and place them on a map with the name of the place it corresponds to. I'm able to put one place's lat and long coordinates but I'm not too sure how to add all of them to a map. I can't get my head around how to do it. I've tried to use a for loop to do it but I'm too sure on how I would implement it. This is what I've got so far:
func getData() {
let url = "https://www.givefood.org.uk/api/2/foodbanks/"
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { [self] data, response, error in
guard let data = data, error == nil else {
print("Wrong")
return
}
var result: [Info]?
do {
result = try JSONDecoder().decode([Info].self, from: data)
}
catch {
print("Failed to convert: \(error.localizedDescription)")
}
guard let json = result else {
return
}
for each in json {
var each = 0
each += 1
let comp = json[each].lat_lng?.components(separatedBy: ",")
let latString = comp![each]
let lonString = comp![each]
let lat = Double(latString)
let lon = Double(lonString)
let locationPin: CLLocationCoordinate2D = CLLocationCoordinate2DMake(lat!, lon!)
let location: CLLocationCoordinate2D = CLLocationCoordinate2DMake(51.55573, -0.108312)
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMetres, longitudinalMeters: regionInMetres)
mapView.setRegion(region, animated: true)
let myAn1 = MapPin(title: json[each].name!, locationName: json[each].name!, coordinate: locationPin)
mapView.addAnnotations([myAn1])
}
})
task.resume()
}
Your loop is wrong, each after for is one Info item. The Int index each is pointless and you set it in each iteration to zero so you get always the same coordinate (at index 1).
First of all declare name and lat_lng as non-optional. All records contain both fields.
struct Info : Decodable {
let lat_lng : String
let name : String
}
Second of all for convenience reasons extend CLLocationCoordinate2D to create a coordinate from a string
extension CLLocationCoordinate2D {
init?(string: String) {
let comp = string.components(separatedBy: ",")
guard comp.count == 2, let lat = Double(comp[0]), let lon = Double(comp[1]) else { return nil }
self.init(latitude: lat, longitude: lon )
}
}
Third of all put all good code into the do scope instead of dealing with optionals and set the region once before the loop
func getData() {
let url = "https://www.givefood.org.uk/api/2/foodbanks/"
let task = URLSession.shared.dataTask(with: URL(string: url)!, completionHandler: { [self] data, response, error in
if let error = error { print(error); return }
do {
let result = try JSONDecoder().decode([Info].self, from: data!)
let location = CLLocationCoordinate2D(latitude: 51.55573, longitude: -0.108312)
let region = MKCoordinateRegion.init(center: location, latitudinalMeters: regionInMetres, longitudinalMeters: regionInMetres)
mapView.setRegion(region, animated: true)
var pins = [MapPin]()
for info in result {
if let coordinate = CLLocationCoordinate2D(string: info.lat_lng) {
pins.append(MapPin(title: info.name, locationName: info.name, coordinate: coordinate))
}
}
DispatchQueue.main.async {
self.mapView.addAnnotations(pins)
}
}
catch {
print("Failed to convert: \(error)")
}
})
task.resume()
}

Unable to get city name by current latitude and longitude in swift

I'm trying to get city name from my current location coordiate by using CLGeocoder().reverseGeocodeLocation.
It gives me country name, street name, state and many other things but not city. Is there anything wrong with my code?
Here's my code:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let location = locations[0]
CLGeocoder().reverseGeocodeLocation(location) { (placeMark, error) in
if error != nil{
print("Some errors: \(String(describing: error?.localizedDescription))")
}else{
if let place = placeMark?[0]{
print("country: \(place.administrativeArea)")
self.lblCurrentLocation.text = place.administrativeArea
}
}
} }
I use the below code too. But doesn't work for me. Here's the another way.
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: (self.locationManager.location?.coordinate.latitude)!, longitude: (self.locationManager.location?.coordinate.longitude)!)
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, error) -> Void in
// Place details
var placeMark: CLPlacemark!
placeMark = placemarks?[0]
// Address dictionary
print(placeMark.addressDictionary as Any)
// Location name
if let locationName = placeMark.addressDictionary!["Name"] as? NSString {
print("locationName: \(locationName)")
}
// Street address
if let street = placeMark.addressDictionary!["Thoroughfare"] as? NSString {
print("street: \(street)")
}
// City
if let city = placeMark.addressDictionary!["City"] as? NSString {
print("city : \(city)")
}
// Zip code
if let zip = placeMark.addressDictionary!["ZIP"] as? NSString {
print("zip :\(zip)")
}
// Country
if let country = placeMark.addressDictionary!["Country"] as? NSString {
print("country :\(country)")
}
})
Please someone help me to get city name.
The field is called locality
if let locality = placeMark.addressDictionary!["locality"] as? NSString {
print("locality :\(locality)")
}
Locality Apple docs
https://developer.apple.com/documentation/corelocation/clplacemark/1423507-locality?language=objc
CLPlacemark
https://developer.apple.com/documentation/corelocation/clplacemark?language=objc
Update:
Try this
import Foundation
import CoreLocation
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: 40.730610, longitude: -73.935242) // <- New York
geoCoder.reverseGeocodeLocation(location, completionHandler: { (placemarks, _) -> Void in
placemarks?.forEach { (placemark) in
if let city = placemark.locality { print(city) } // Prints "New York"
}
})

How to Force Default Language to English in CLLocationManager?

SO, I am new to swift and I made the conversion from current Lat and Long to City name and Country, it works fine like that:
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
if didFindLocation == false
{
didFindLocation = true
locationManager.stopUpdatingLocation()
userLocation = locations[0]
long = userLocation.coordinate.longitude;
lat = userLocation.coordinate.latitude;
print("\(lat),\(long)")
converLocationToCity()
}
}
func converLocationToCity()
{
let geoCoder = CLGeocoder()
userLocation = CLLocation(latitude: self.lat, longitude: self.long)
geoCoder.reverseGeocodeLocation(userLocation, completionHandler:
{
(placemarks, error) -> Void in
var placeMark: CLPlacemark!
placeMark = placemarks?[0]
if let city = placeMark.addressDictionary!["State"] as? String
{
self.city = city as String
} else
{
self.city = ""
}
if let country = placeMark.addressDictionary!["Country"] as? String
{
self.country = country as String
} else
{
self.country = ""
}
self.currentCity.name = ("\(self.city), \(self.country)" as String)
print("\(self.currentCity.name)")
self.fetchWeather.performCurrentWeatherFetch(forSelectedCity: self.currentCity.name)
DispatchQueue.main.async()
{
(self.superview as! UICollectionView).reloadData()
}
})
}
But when the device is set to other language, Russian for example it returns me the City Name and Country in Russian characters, but I need it to be only in english, please anybody some ideas or suggestions? Thank you!
Here is My Solution
While getting the location data i change `UserDefaults.standard.set(["base"], forKey: "AppleLanguages")'
and once I have received the dictionary in English i remove the Object
UserDefaults.standard.removeObject(forKey: "AppleLanguages")
which then sets applelanguage to default value
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
let userLocation:CLLocation = locations[0] as CLLocation
// Call stopUpdatingLocation() to stop listening for location updates,
// other wise this function will be called every time when user location changes.
// manager.stopUpdatingLocation()
print("user latitude = \(userLocation.coordinate.latitude)")
print("user longitude = \(userLocation.coordinate.longitude)")
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: userLocation.coordinate.latitude, longitude: userLocation.coordinate.longitude)
//location.accessibilityLanguage = "en-US"
UserDefaults.standard.set(["base"], forKey: "AppleLanguages")
geoCoder.reverseGeocodeLocation(location, completionHandler: { placemarks, error in
guard let addressDict = placemarks?[0].addressDictionary else {
return
}
print(addressDict)
// Print each key-value pair in a new row
addressDict.forEach { print($0) }
// Print fully formatted address
if let formattedAddress = addressDict["FormattedAddressLines"] as? [String] {
print(formattedAddress.joined(separator: ", "))
}
// Access each element manually
if let locationName = addressDict["Name"] as? String {
print(locationName)
}
if let street = addressDict["Thoroughfare"] as? String {
print(street)
}
var myCity:String = ""
if let city = addressDict["City"] as? String {
print(city)
if(city != "" ){
myCity = city
}
}
if let zip = addressDict["ZIP"] as? String {
print(zip)
}
var myCountry:String = ""
if let country = addressDict["Country"] as? String {
print(country)
if(country != "" ){
myCountry = country
}
MyGenericFunctions.sharedInstance.saveCountry(country: country)
}
manager.stopUpdatingLocation()
if(myCity != "" && myCountry != "" && self.isCurrLocAPICalled != true){
print("API Called")
self.isCurrLocAPICalled = true
self.callLocationSearch(strCity: myCity, strCountry: myCountry)
UserDefaults.standard.removeObject(forKey: "AppleLanguages")
}
})
//manager.stopUpdatingLocation()
}

addressDictionary returns random nil

I've used this code for my App. Its works great but sometime crashes, after +/- 50 secondes of tracking my route. I know it has something to do with the optionals "?", but I can't get it to work.
I get the following message:
fatal error: unexpectedly found nil while unwrapping an Optional
Part where the code breaks:
if let locationName = placeMark.addressDictionary?["Name"] as? NSString
{
print(locationName)
self.locationName = locationName as String
}
Full locationManager code:
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
for location in locations as [CLLocation] {
let howRecent = location.timestamp.timeIntervalSinceNow
//start motion tracker
motionTracker()
//location name tracker
let locValue:CLLocationCoordinate2D = manager.location!.coordinate
let latitude: CLLocationDegrees = locValue.latitude
let longitude: CLLocationDegrees = locValue.longitude
let geoCoder = CLGeocoder()
let location = CLLocation(latitude: latitude, longitude: longitude)
geoCoder.reverseGeocodeLocation(location)
{
(placemarks, error) -> Void in
let placeArray = placemarks as [CLPlacemark]!
// Place details
var placeMark: CLPlacemark!
placeMark = placeArray?[0]
// Address dictionary
print(placeMark.addressDictionary)
// Location name
if let locationName = placeMark.addressDictionary?["Name"] as? NSString
{
print(locationName)
self.locationName = locationName as String
}
// Street address
if let street = placeMark.addressDictionary?["Thoroughfare"] as? NSString
{
//print(street)
self.locationStreet = street as String
}
// City
if let city = placeMark.addressDictionary?["City"] as? NSString
{
self.locationCity = city as String
//print(city)
}
// Zip code
if let zip = placeMark.addressDictionary?["ZIP"] as? NSString
{
//print(zip)
}
// Country
if let country = placeMark.addressDictionary?["Country"] as? NSString
{
//print(country)
}
}
I suspect you’re hitting a case where Google doesn’t have a reverse geocode result for your location and is returning an empty array.
Instead of:
let placeArray = placemarks as [CLPlacemark]!
var placemark: CLPlacemark!
placemark = placeArray?[0]
… which assumes that there will be an array and it will always contain at least one element, use:
if let placemark = placemarks?.first {
// Rest of your code
}

Resources