Why does Swift not update the variables outside the closure? - swift

Here's the code I'm using -- Problem is that the value of latitude and longitude in the returned CLLocationCoordinate2D object are both -1, their initialized values. What am I missing?
func getLocationInfoForAddress(shop: store) -> CLLocationCoordinate2D {
var address = getAddressInOneLine(shop)
var latitude: CLLocationDegrees = -1
var longitude: CLLocationDegrees = -1
var geocoder = CLGeocoder()
geocoder.geocodeAddressString(address, {(placemarks: [AnyObject]!, error: NSError!) -> Void in
if let placemark = placemarks?[0] as? CLPlacemark {
latitude = placemark.location.coordinate.latitude
longitude = placemark.location.coordinate.longitude
}
})
var location: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: latitude,longitude: longitude)
return location
}

As a complement to #NateCook's answer, one possible way to refactor your code is:
func getLocationInfoForAddress(shop: store) {
var address = getAddressInOneLine(shop)
var geocoder = CLGeocoder()
geocoder.geocodeAddressString(address, {(placemarks: [AnyObject]!, error: NSError!) -> Void in
if let placemark = placemarks?[0] as? CLPlacemark {
var latitude = placemark.location.coordinate.latitude
var longitude = placemark.location.coordinate.longitude
var location: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: latitude,longitude: longitude)
self.didReceiveGeocodeAddress(location)
}
})
}
func didReceiveGeocodeAddress(location: CLLocationCoordinate2D) {
// do something
}
When the location is obtained, you invoke a method of the same class passing the location. Since the handler closure is executed in the main thread, you can safely update UI components.

The geocodeAddressString(:completionHandler:) method is asynchronous:
This method submits the specified location data to the geocoding server asynchronously and returns. Your completion handler block will be executed on the main thread. After initiating a forward-geocoding request, do not attempt to initiate another forward- or reverse-geocoding request.
So it is being executed after you've created location and returned it from your function. You'll need to refactor your code to handle this asynchronously.

Related

Swift: Accessing argument of closure stored as variable

I ran into some code for a location manager class and I noticed that there is a variable that holds a closure.
var locationInfoCallBack: ((_ info: LocationInformation) -> ())!
I can't seem to access the underlying arguments from the variable, which would be preferred. Is it possible to retrieve the LocationInformation from the above variable?
My understanding is that by passing in the info parameter like this self.locationInfoCallBack(info) the 'start' function will re-run:
In my experience it is unnecessary to recall locationManager.requestAlwaysAuthorization() or locationManager.startUpdatingLocation()
Here's the code:
class Location: NSObject, CLLocationManagerDelegate {
static let shared = Location()
let locationManager : CLLocationManager
var locationInfoCallBack: ((_ info: LocationInformation) -> ())!
override private init() {
locationManager = CLLocationManager()
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.distanceFilter = kCLLocationAccuracyNearestTenMeters
super.init()
locationManager.delegate = self
}
func start(completion: #escaping(_ info: LocationInformation) -> Void) {
self.locationInfoCallBack = completion
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
func stop() {
locationManager.stopUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let mostRecentLocation = locations.last else {
return
}
let info = LocationInformation()
info.latitude = mostRecentLocation.coordinate.latitude
info.longitude = mostRecentLocation.coordinate.longitude
let geocoder = CLGeocoder()
geocoder.reverseGeocodeLocation(mostRecentLocation) { (placemarks, error) in
guard let placemarks = placemarks, let placemark = placemarks.first else { return }
if let city = placemark.locality,
let state = placemark.administrativeArea,
let zip = placemark.postalCode,
let locationName = placemark.name,
let thoroughfare = placemark.thoroughfare,
let country = placemark.country {
info.city = city
info.state = state
info.zip = zip
info.address = locationName + ", " + (thoroughfare as String)
info.country = country
}
self.locationInfoCallBack(info)
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
locationManager.stopUpdatingLocation()
}
}
class LocationInformation {
var city: String?
var address: String?
var latitude: CLLocationDegrees?
var longitude: CLLocationDegrees?
var zip: String?
var state: String?
var country: String?
init(city: String? = "", address: String? = "", latitude: CLLocationDegrees? = Double(0.0), longitude: CLLocationDegrees? = Double(0.0), zip: String? = "", state: String? = "", country: String? = "") {
self.city = city
self.address = address
self.latitude = latitude
self.longitude = longitude
self.zip = zip
self.state = state
self.country = country
}
}
If the underlying argument is inaccessible my current hypothesis is the variable 'locationInfoCallBack' could be used to check whether the 'start' function has completed. If this is the case why not use a simple boolean for the same result?
I want it to be available app wide
In that case, a more suitable design would be not to use a closure. Replace the closure with:
var currentLocationInfo: LocationInformation?
The parameter to start can be removed:
func start() {
locationManager.requestAlwaysAuthorization()
locationManager.startUpdatingLocation()
}
And in didUpdateLocation, replace the call self.locationInfoCallBack(info) with a simple assignment to currentLocationInfo:
self.currentLocationInfo = info
Now you can access from anywhere:
Location.shared.currentLocationInfo
This will give you the location that you got, the last time that didUpdateLocation is called, and you successfully reverse-geocoded that.
Note that this could be nil, because you might be accessing it before the first time that you successfully. reverse-geocoded a location.
You can access the parameter when the closure is running from the code of the closure, and its value will be whatever the caller passed. It’s the same as with a function. Unless the function (or closure) is running, the parameters don’t exist.

Swift mutating function struct pass struct’s variable name in function?

I am working in Swift trying to update an organization struct that will need to hold a latitude and longitude. I created a mutating function in the struct that will update the latitude and longitude based on the organization organization struct’s address. I got it to work, but the issue is that when I call the mutating function, I need to manually enter the variable name with the .latitude and .longitude. Is there a way that I can pass the variable struct’s name automatically and reference the .latitude and .longitude without calling the specific variable name with it so I can make it more usable? I included an example below with my code. Thanks for your help!
import UIKit
import PlaygroundSupport
import CoreLocation
PlaygroundPage.current.needsIndefiniteExecution = true
struct organization {
var name: String
var address: String
var latitude: CLLocationDegrees = 0 //default setting for latitude
var longitude: CLLocationDegrees = 0 //default setting for longitude
mutating func getCoordinateFrom(completion: #escaping(_ coordinate: CLLocationCoordinate2D?, _ error: Error?) -> () ) {
CLGeocoder().geocodeAddressString(address) { placemarks, error in
completion(placemarks?.first?.location?.coordinate, error)
}
}
}
struct Coordinates {
var latitude: Double
var longitude: Double
}
//create an wildernessLodge variable of type organization
var wildernessLodge = organization(name: "Disney's Wilderness Lodge", address: "901 Timberline Dr, Orlando, FL 32830")
wildernessLodge.getCoordinateFrom { coordinate, error in
guard let coordinate = coordinate, error == nil else { return }
wildernessLodge.latitude = coordinate.latitude
wildernessLodge.longitude = coordinate.longitude
print("update 1 \(wildernessLodge)")
}
I'm a bit confused by your code. Why is getCoordinateFrom marked as mutating? Perhaps you meant to write something like this.
mutating func getCoordinatesFromAddress() {
CLGeocoder().geocodeAddressString(address) { placemarks, error in
guard let coordinate = placemarks?.first?.location?.coordinate, error == nil else { return }
self.latitude = coordinate.latitude
self.longitude = coordinate.longitude
}
}
Now this function is mutating (it modifies self), and
wildernessLodge.getCoordinateFrom { coordinate, error in ... }
can be replaced with
wildernessLodge.getCoordinatesFromAddress()
The only reason to leave the getCoordinateFrom method is if, somewhere in your code, you intend to get coordinates from an address but not update the coordinates in the struct. I can't imagine a good reason to do that, so I would recommend replacing the getCoordinateFrom method with something else.
Alternatively, if you generally intend to set the coordinates right after creating a value of this type, you might want to consider something like this.
init(name: String, address: String) {
self.name = name
self.address = address
CLGeocoder().geocodeAddressString(address) { placemarks, error in
guard let coordinate = placemarks?.first?.location?.coordinate, error == nil else { return }
self.latitude = coordinate.latitude
self.longitude = coordinate.longitude
}
}
or
init(name: String, address: String) {
self.name = name
self.address = address
self.getCoordinatesFromAddress()
}
Then, you could create an organization using organization(name: name, address: address) and the coordinates would automatically be set correctly.
If neither of these are satisfactory, maybe you should create two different structs to capture the behavior you want.
struct Organization {
var name: String
var address: String
func withCoordinates(completion: #escaping(_ coordinate: CLLocationCoordinate2D?, _ error: Error?) -> () ) {
CLGeocoder().geocodeAddressString(address) { placemarks, error in
completion(placemarks?.first?.location?.coordinate, error)
}
}
}
struct OrganizationWithCoordinates {
var name: String
var address: String
var latitude: CLLocationDegrees
var longitude: CLLocationDegrees
init(from organization: Organization) {
self.name = organization.name
self.address = organization.address
organization.withCoordinates { coordinate, error in
guard let coordinate = coordinate, error == nil else { return }
self.latitude = coordinate.latitude
self.longitude = coordinate.longitude
}
}
}
I would prefer an approach like this, but I like having lots of types.
Finally, as noted in the comments, if you are really just concerned with brevity, you can replace
var latitude: CLLocationDegrees
var longitude: CLLocationDegrees
with
var coordinates: CLLocationCoordinate2D
var latitude: CLLocationDegrees { coordinates.latitude }
var longitude: CLLocationDegrees { coordinates.longitude }
and then replace
wildernessLodge.latitude = coordinate.latitude
wildernessLodge.longitude = coordinate.longitude
with
wildernessLodge.coordinates = coordinate
In fact, you should feel free to combine any of these approaches.
Edit: As pointed out, these solutions do not work as-is. The fundamental tension is trying to work with CLGeocoder's async method synchronously. One solution is to use a class instead of a struct. The other approach is to use a modification of the withCoordinate method above:
struct Organization {
var name: String
var address: String
func withCoordinate(callback: #escaping (OrganizationWithCoordinate?, Error?) -> Void) {
CLGeocoder().geocodeAddressString(self.address) { placemarks, error in
if let coordinate = placemarks?.first?.location?.coordinate, error == nil {
let orgWithCoord = OrganizationWithCoordinate(name: self.name, address: self.address, latitude: coordinate.latitude, longitude: coordinate.latitude)
callback(orgWithCoord, nil)
} else {
callback(nil, error)
}
}
}
}
struct OrganizationWithCoordinate {
var name: String
var address: String
var latitude: CLLocationDegrees
var longitude: CLLocationDegrees
}
Organization(name: "Disney's Wilderness Lodge", address: "901 Timberline Dr, Orlando, FL 32830").withCoordinate { orgWithCoord, error in
guard let orgWithCoord = orgWithCoord, error == nil else {
print("Error")
return
}
print(orgWithCoord)
}
This embraces the async nature of CLGeocoder.
Another solution could be to force CLGeocoder to be synchronous using DispatchSemaphore as follows. I don't think these work correctly in Playgrounds, but this should work in an actual app.
struct Organization {
var name: String
var address: String
var latitude: CLLocationDegrees
var longitude: CLLocationDegrees
init(name: String, address: String) throws {
self.name = name
self.address = address
var tempCoordinate: CLLocationCoordinate2D?
var tempError: Error?
let sema = DispatchSemaphore(value: 0)
CLGeocoder().geocodeAddressString(address) { placemarks, error in
tempCoordinate = placemarks?.first?.location?.coordinate
tempError = error
sema.signal()
}
// Warning: Will lock if called on DispatchQueue.main
sema.wait()
if let error = tempError {
throw error
}
guard let coordinate = tempCoordinate else {
throw NSError(domain: "Replace me", code: -1, userInfo: nil)
}
self.longitude = coordinate.longitude
self.latitude = coordinate.latitude
}
}
// Somewhere in your app
let queue = DispatchQueue(label: "Some queue")
queue.async {
let wildernessLodge = try! Organization(name: "Disney's Wilderness Lodge", address: "901 Timberline Dr, Orlando, FL 32830")
DispatchQueue.main.async {
print(wildernessLodge)
}
}
Here, a new queue to do Organization related work is created to avoid locking up the main queue. This method creates the least clunky-looking code in my opinion, but probably is not the most performant option. The location APIs are async for a reason.

Swift Geocoder won't geocode address

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

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 Completion Handler for Reverse Geocoding

I have been banging my head in order to figure out how to fix this piece of code. Basically I have a piece of code that takes a string cityName and stores the latitude and longitude of it in a global variable and call it in another function right after. Apparently because of asynchronous call, I am not able to do that and the value of longitude and latitude are nil.
func findCityCoordinates(cityName: String) {
var geocoder = CLGeocoder()
geocoder.geocodeAddressString(cityName, completionHandler: {(placemarks: [AnyObject]!, error: NSError!) -> Void in
if let placemark = placemarks?[0] as? CLPlacemark {
self.cityLatitude = placemark.location.coordinate.latitude //Returns nil
self.cityLongitude = placemark.location.coordinate.longitude //Returns nil
}
})
}
I have also been trying to work around completion handler but I have no idea on how to implement it and call it in a function. I would appreciate some help.
I was able to use the dispatch async for this. I declared two variables above the geocoder, assign them inside, and use them after it completes.
var lat:Float!
var long:Float!
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(cityName, completionHandler: {(placemarks: [AnyObject]!, error: NSError!) -> Void in
if let placemark = placemarks?[0] as? CLPlacemark {
lat = Float(placemark.location.coordinate.latitude)
long = Float(placemark.location.coordinate.longitude)
}
dispatch_async(
dispatch_get_main_queue(), {
self.cityLatitude = lat
self.cityLongitude = long
})
})
...stores the latitude and longitude of it in a global variable and call it in another function right after
I suspect that the other function is using it before the geocoder completion block sets the values. Put the call to the other function to the completion block, if possible.
func findCityCoordinates(cityName: String) {
var geocoder = CLGeocoder()
geocoder.geocodeAddressString(cityName, completionHandler: {(placemarks: [AnyObject]!, error: NSError!) -> Void in
if let placemark = placemarks?[0] as? CLPlacemark {
self.cityLatitude = placemark.location.coordinate.latitude //Returns nil
self.cityLongitude = placemark.location.coordinate.longitude //Returns nil
*** <-- call your function that uses location here --> ***
}
})
}
Well my brother helped me a little bit out on this, basically I wanted to run a completion block in the IBAction of the save button instead inside of the function findCityCoordinates:
func findCityCoordinate(city: String, completionHandler: (coordinate: CLLocationCoordinate2D) -> ()) {
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(city) { (placemarks: [AnyObject]!, error: NSError!) -> () in
       if let placemark = placemarks[0] as? CLPlacemark {
           let coordinate = placemark.location.coordinate
          completionHandler(coordinate: coordinate)
         }
     }
}
And heres the function being called inside the saveButton action outlet:
findCityCoordinates(searchBar.text) { (cityCoordinate: CLLocationCoordinate2D) -> () in
self.queryForTable(cityCoordinate)
// Force reload of table data
self.myTableView.reloadData()
}