SwiftUI async data receive from #escaping closure - swift

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.

Related

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.

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
}
})

unexpectedly found nil unexpectedly - struct init? not working

I am working on a demo app to return a list of restaurant with the google places API.
I have a Restaurant class - 1 property is a custom type RestaurantDetails. This is where the challenge is:
Restaurant.swift
class Restaurant {
var id:String
var placeId:String
var name:String
var location: Location //Location + address + coordinate + distance
var phone:String?
let details : RestaurantDetails
init(id:String, placeId:String, name:String, location: Location, details: RestaurantDetails) {
self.id = id
self.placeId = placeId
self.name = name
self.location = location
self.details = details
}
convenience init(dict:[String:Any]) {
let id = dict["id"] as! String
let placeId = dict["place_id"] as! String
let name = dict["name"] as! String
let address = dict["formatted_address"] as? String
let location = Location(address: address!, json: dict["geometry"] as! [String : Any])
if let price = dict["price_level"] as? Double {
print("price => \(Price(valueDouble: price))")
}
if let rating = dict["rating"] as? Double {
print("rating => \(Rating(valueDouble: rating))")
}
self.init(id: id, placeId: placeId, name: name, location: location!, details: RestaurantDetails(json: dict)!)
}
}
the initialization of the details property fails each time
even using the breakpoint does not help to debug the error because the custom init? does not seem to fire. I do not see any syntax error though
I am usually using guard let control flow to parse the JSON format if the value exists. unsuccessful.
the breakpoint does not kick in on the RestaurantDetails.swift file
RestaurantDetails.swift
enum Price {
case cheap, expensive, `default`
init(valueDouble: Double) {
switch valueDouble {
case 0..<2: self = .cheap
case 2..<4 : self = .expensive
default: self = .default
}
}
var dollarSymbol : String {
switch self {
case .cheap: return "$"
case .expensive: return "$$"
default: return ""
}
}
}
enum Rating {
case low, fair, good, excellent, `default`
init(valueDouble: Double) {
switch valueDouble {
case 0..<2: self = .low
case 3..<4: self = .fair
case 4..<5: self = .good
case 6 : self = .excellent
default: self = .default
}
}
var starSymbol : String {
switch self {
case .low: return "⭐"
case .fair: return "⭐⭐"
case .good: return "⭐⭐⭐"
case .excellent: return "⭐⭐⭐⭐"
default: return ""
}
}
}
struct RestaurantDetails {
let price:Price
let rating:Rating // Rating (enum) (filter)
let openNow: Bool //(filter)
let types : [String]?
let photos : [NSDictionary]?
}
extension RestaurantDetails {
init?(json: [String: Any]) {
guard let price = json["price_level"] as? Double,
let rating = json["rating"] as? Double,
let openingHours = json["opening_hours"] as? NSDictionary,
let types = json["types"] as? [String],
let photos = json["photos"] as? [NSDictionary] else {
return nil
}
print("json details : \(json)")
self.price = Price(valueDouble: price)
self.rating = Rating(valueDouble: rating)
self.openNow = openingHours["open_now"] as! Bool != nil ?? false
self.types = types
self.photos = photos
}
}
I also have a Location.swift file that works perfectly
struct Location {
var address :String
var coordinate:(lat:Double, lng:Double)
}
extension Location {
init?(address: String, json:[String:Any]) {
guard let latitude = json["location"] as? NSDictionary, let longitude = json["location"] as? NSDictionary else { return nil }
self.init(address: address, coordinate: (lat: latitude["lat"] as! Double, lng: longitude["lng"] as! Double))
}
}
As vadian described in his comment, the issue causing the crash is that location has a failable initializer, you are forcibly unwrapping it, and since initialization is in fact failing, you have now force-unwrapped a nil value.
The broader questions are: what do you, the developer, want to happen when some or all location data is not available, and what is the best way to go about doing that?
Options for handling incomplete or missing location data
Based on your description of the problem (“a demo app to return a list of restaurant with the google places api”), it is not clear whether or not restaurants without useable location data should enter your model. It is also not clear whether or not there is some location data you could do without. So I see three options:
Discard all restaurants with incomplete location data
Discard some restaurants with incomplete location data while keeping others
Keep all restaurants regardless of location data
To implement these options, you may want to use some common tools for dealing with optionals:
Nil-coalescing operator
a = b ?? c
Set a equal to b, unless b is nil, in which case set a equal to c.
Optional binding in guard and if
guard let a = b else { return }
Set a equal to b unless b is nil, in which case return from the current context. You can do work in the else branch prior to returning.
if let a = b { /* Do work using a */ } else { /* Do other work */ }
If b is not nil, enter the if branch and set a equal to b; if b is nil, enter the else branch.
How to do it
Discard all restaurants with incomplete location data
In this case, you could make a failable initializer for Restaurant, guarded by location being non-nil:
/// Initialization fails if `location` is `nil`
convenience init?(id:String, placeId:String, name:String, location: Location?, details: RestaurantDetails) {
guard let location = location else { return nil }
self.init(id: id, placeId: placeId, name: name, location: location, details: details)
}
Discard some restaurants with incomplete location data while keeping others
You may be able to recover from some kinds of missing location data. For example, it may be the case that you really just need the street address string and the latitude and longitude are inessential. You could rewrite your Location struct to reflect that:
struct Location {
var address: String
var coordinate: (lat: Double, lng: Double)?
}
extension Location {
init?(address: String, json:[String:Any]) {
let coordinate: (Double, Double)?
if let latitude = json["location"] as? NSDictionary, let longitude = json["location"] as? NSDictionary {
coordinate = (latitude, longitude)
} else {
coordinate = nil
}
self.init(address: address, coordinate: coordinate)
}
}
Alternative, lat/long might be the important part, but it might sometimes be possible to recover them from the street address using another API:
struct Location {
var address: String
var coordinate: (lat: Double, lng: Double)
}
extension Location {
init?(address: String, json:[String:Any]) {
let coordinate: (Double, Double)?
if let latitude = json["location"] as? NSDictionary, let longitude = json["location"] as? NSDictionary {
coordinate = (latitude, longitude)
} else {
/* An API call that takes an address string and tries to return lat/long but might return nil */
coordinate = returnedCoordinateFromAPI
}
guard let coordinate = coordinate else { return nil }
self.init(address: address, coordinate: coordinate)
}
}
Keep all restaurants regardless of location data
This is the easiest case. If location is not essential, make it optional:
class Restaurant {
var id: String
var placeId: String
var name: String
var location: Location? //Location + address + coordinate + distance
var phone: String?
let details: RestaurantDetails
init(id:String, placeId:String, name:String, location: Location?, details: RestaurantDetails) {
self.id = id
self.placeId = placeId
self.name = name
self.location = location
self.details = details
}
convenience init?(dict:[String:Any]) {
guard let id = dict["id"] as? String,
let placeId = dict["place_id"] as? String,
let name = dict["name"] as? String else { return nil }
// Use the address from the dictionary unless it is nil, in which case substitute an empty string
let address = dict["formatted_address"] as? String ?? ""
let location = Location(address: address, json: dict["geometry"] as? [String : Any])
if let price = dict["price_level"] as? Double {
print("price => \(Price(valueDouble: price))")
}
if let rating = dict["rating"] as? Double {
print("rating => \(Rating(valueDouble: rating))")
}
guard let details = RestaurantDetails(json: dict) else { return nil }
self.init(id: id, placeId: placeId, name: name, location: location, details: details)
}
}

swift 3 Calculate distance to current location and sort result from closet to furthest

I'm trying to to calculate the distance from an event to my current location, sort the results and populate that in a tableview. I keep getting error for optional unwrapped value distance is nil.
private func observeEvents() {
refHandle = ref.observe(.childAdded, with: { (snapshot) -> Void in
let eventDetails = snapshot.value as! Dictionary<String, AnyObject>
let eventID = snapshot.key
let location = eventDetails["location"] as! String!
//calculating distance
self.forwardGeocoding(address: location!)
let distance = self.eventLocation?.distance(from: self.currentLocation!) as Double!
//end calculating
let dateTime = eventDetails["dateTime"] as! String!
let addedByUser = eventDetails["addedByUser"] as! String!
let attending = eventDetails["attendance"] as! String!
if let name = eventDetails["eventName"] as! String! , name.characters.count > 0
{
self.events.append(Events(id:eventID, name: name, location: location!, dateTime: dateTime!, addedByUser: addedByUser!, attending: attending! , distance: distance!))
self.events.sort(by: { $0.distance < $1.distance})
self.tableView.reloadData()
} else {
print("Error ! Can't load events from database")
}
})
} //load events data to uitableview
I created a function to return a CLLocation from an address
func forwardGeocoding(address: String) {
CLGeocoder().geocodeAddressString(address, completionHandler: { (placemarks, error) in
if error != nil {
print(error!)
return
}
if (placemarks?.count)! > 0 {
let placemark = placemarks?[0]
self.eventLocation = placemark?.location
}
})
}
I finally figured out the answer. The issue was the function for distance is called asynchronously there for the result would always be nil. I created a completion handler for the forwardGeocoding function to return latitude and longitude from the address string and call the result inside the nested firebase listener. Here is the code, I hope if someone ran into something similar problem to me will find it helpful.
//Get lat and long
func getCoordinates(address: String, completionHandler: #escaping (_ lat: CLLocationDegrees?, _ long: CLLocationDegrees?, _ error: Error?) -> ()) -> Void {
var _:CLLocationDegrees
var _:CLLocationDegrees
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(address) { (placemarks: [CLPlacemark]!, error: Error!) in
if error != nil {
print("Geocode failed with error: \(error.localizedDescription)")
} else if placemarks.count > 0 {
let placemark = placemarks[0] as CLPlacemark
let location = placemark.location
let lat = location?.coordinate.latitude
let long = location?.coordinate.longitude
completionHandler(lat, long, nil)
}
}
}
Nested call in firebase listener
refHandle = ref.observe(.childAdded, with: { (snapshot) -> Void in
let location = event["address"] as! String
self.getCoordinates(address: location!) { lat, long, error in
if error != nil {
print("Error")
} else {
self.latitude = lat
self.longitude = long
let distance = CLLocation(latitude: self.latitude!,longitude: self.longitude!).distance(from: self.currentLocation!)
if let name = eventDetails["eventName"] as! String! , name.characters.count > 0
{
self.events.append(Events(id:eventID, name: name, location: location!, dateTime: dateTime!, addedByUser: addedByUser!, attending: attending!, distance: distance))
self.events.sort(by: { $0.distance < $1.distance})
self.tableView.reloadData()
} else {
print("Error ! Can't load events from database")
}
}
}
})

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.