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

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.

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.

Retrieving coordinates from address retuning zero

I'm trying to play with SwiftUI and make a map with dropped pins from locations (generated from a database API).
I have my struct:
struct Locations: Decodable, Identifiable {
var id: Int { _id }
let _id: Int // the one used in the database
let streetaddress: String?
let suburb: String?
let state: String?
let postcode: String?
// get the co-ordinates now
var coordinates: CLLocationCoordinate2D? {
let geocoder = CLGeocoder()
var output = CLLocationCoordinate2D()
if let address = streetaddress,
let suburb = suburb,
let postcode = postcode,
let state = state {
let fullAddress = "\(address) \(suburb), \(state) \(postcode)"
geocoder.geocodeAddressString( String(fullAddress) ) { ( placemark, error ) in
if let latitude = placemark?.first?.location?.coordinate.latitude,
let longitude = placemark?.first?.location?.coordinate.longitude {
output = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
}
}
return output
}
}
However, whenever I call the coordinates I am getting a {"msg":"#NullIsland Received a latitude or longitude from getLocationForBundleID that was exactly zero", "latIsZero":0, "lonIsZero":0} error.
I have added the error snippet from here: https://stackoverflow.com/a/65837163/1086990 to dive deeper into the error, and it is returning network: network was unavailable or a network error occurred.
I am able to call Map() in the view, and permissions are set in the info.plist as well as see my current location, etc.
Is there something I'm missing or is it not calculating because the Strings are all optional? Been trying to understand how it's not generating the coordinates from the address.
If I try to debug in the view I tried this:
struct MapView: View {
var body: some View {
Text("Hello World")
.onAppear {
for l in modelData.locations {
print( String("\(l.coordinates)") )
}
}
}
}
// console
// Optional(__C.CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0))
// ...
// Optional(__C.CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0))
The issue there is that geocodeAddressString is an asynchronous method. You are returning the value before receiving the result. What you need is a method instead of a computed property and a completion handler.
func coordinate(completion: #escaping (CLLocationCoordinate2D?, Error?) -> Void) {
let streetAddress = streetAddress ?? ""
let suburb = suburb ?? ""
let postCode = postCode ?? ""
let state = state ?? ""
let fullAddress = "\(streetAddress) \(suburb), \(state) \(postCode)"
CLGeocoder().geocodeAddressString(fullAddress) { completion($0?.first?.location?.coordinate, $1) }
}
Usage:
#State var location = Location(id: 1, streetAddress: "One Infinite Loop", suburb: "Cupertino", state: "CA", postCode: "95014")
var body: some View {
Text("Hello, world!")
.padding()
.onAppear {
location.coordinate { coordinate, error in
guard let coordinate = coordinate else {
print("error:", error ?? "nil")
return
}
print("coordinate", coordinate)
}
}
}
This will print
coordinate CLLocationCoordinate2D(latitude: 37.331656, longitude: -122.0301426)
as mentioned geocodeAddressString is an asynchronous function. That means you have to use some completion handler. So you cannot use it like you do with coordinates.
Here is some very basic code using a function instead:
struct Locations: Decodable, Identifiable {
var id: String = UUID().uuidString
// let _id: Int // the one used in the database
let streetaddress: String? = "1 Infinite Loop"
let suburb: String? = "Cupertino"
let state: String? = "CA"
let postcode: String? = ""
func getCoordinates(handler: #escaping ((CLLocationCoordinate2D) -> Void)) {
if let address = streetaddress, let suburb = suburb, let postcode = postcode, let state = state {
let fullAddress = "\(address) \(suburb), \(state) \(postcode)"
CLGeocoder().geocodeAddressString(fullAddress) { ( placemark, error ) in
handler(placemark?.first?.location?.coordinate ?? CLLocationCoordinate2D())
}
}
}
}
struct ContentView: View {
let locs = Locations()
#State var coordString = ""
var body: some View {
Text(locs.streetaddress ?? "no address")
Text(coordString)
.onAppear {
locs.getCoordinates() { coords in
coordString = "\(coords.latitude), \(coords.longitude)"
}
}
}
}

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

How to go through mapView.annotations array with objects of different type?

I have several different custom Annotation objects.
They all extend MKAnnotation class and it's protocol.
This is how at least two of them look like, there are several more of them, and frankly, they all look very similar:
My BaseAnnotation protocol:
import MapKit
/// Defines the shared stuff for map annotations
protocol BaseAnnotation: MKAnnotation {
var imageName: String { get }
var coordinate: CLLocationCoordinate2D { get set }
}
Quest Giver Annotation:
import UIKit
import MapKit
class QuestGiverAnnotation: NSObject, BaseAnnotation {
var imageName = "icon_quest"
var questPin: QuestPin?
#objc var coordinate: CLLocationCoordinate2D
// MARK: - Init
init(forQuestItem item: QuestPin) {
self.questPin = item
if let lat = item.latitude,
lng = item.longitude {
self.coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lng)
} else {
self.coordinate = CLLocationCoordinate2D(latitude: 0, longitude: 0)
}
}
init(location: CLLocationCoordinate2D) {
self.coordinate = location
}
}
Resource Annotation:
import UIKit
import MapKit
class ResourceAnnotation: NSObject, BaseAnnotation {
var imageName: String {
get {
if let resource = resourcePin {
return resource.imageName
} else {
return "icon_wood.png"
}
}
}
var resourcePin: ResourcePin?
#objc var coordinate: CLLocationCoordinate2D
var title: String? {
get {
return "Out of range"
}
}
// MARK: - Init
init(forResource resource: ResourcePin) {
self.resourcePin = resource
if let lat = resource.latitude,
lng = resource.longitude {
self.coordinate = CLLocationCoordinate2D(latitude: lat, longitude: lng)
} else {
self.coordinate = CLLocationCoordinate2D(latitude: 0, longitude: 0)
}
}
init(location: CLLocationCoordinate2D) {
self.coordinate = location
}
}
As I said, there are 2-3 more, but they are more or less same as these two.
Don't ask me why I made them like this...I needed them to be like this.
Ok, problem occurs when I want to list all added map annotations through mapView.annotations.
I get fatal error: NSArray element failed to match the Swift Array Element type
And I understand that it's because annotations array consists of Objects of diferent type, but i am having dificulties getting an object of a certain type.
This is one of the ways I tried to get an annotation I need from the array, ut I have failed:
func annotationExistsAtCoordinates(lat: Double, long:Double) -> Bool{
var annotationExists = false
**let mapAnnotations: [Any] = self.annotations**
for item in mapAnnotations{
if let annotation = item as? QuestGiverAnnotation{
if annotation.coordinate.latitude == lat && annotation.coordinate.longitude == long{
annotationExists = true
}
//let annotation = self.annotations[i] as! AnyObject
}
}
return annotationExists
}
I get error on this line: let mapAnnotations: [Any] = self.annotations
So, my question is: how to get objects of different type from one array without getting a fatal error: NSArray element failed to match the Swift Array Element type ?
Initializing with array of MKAnnotation still throws error.
do:
let mapAnnotations: [AnyObject] = self.annotations
This worked.

Handling multiple optional variables?

I am struggling with a nice pattern about handling multiple optionals in my code and the corresponding error handling.
Hava a look at the following example
func getCoordinates1(pLatitude: Double?, pLongitude: Double?) -> CLLocationCoordinate2D?{
var coord:CLLocationCoordinate2D?
if let latitude = pLatitude {
if let longitude = pLongitude {
coord = CLLocationCoordinate2DMake(lat, long)
}
}
return coord
}
This looks fine, but in a real world, you might need some error handling and here I am looking for a nice way of writing it down without duplicate code:
func getCoordinates2(pLatitude: Double?, pLongitude: Double?) -> CLLocationCoordinate2D? {
var coord:CLLocationCoordinate2D?
if let latitude = pLatitude {
if let longitude = pLongitude {
coord = CLLocationCoordinate2DMake(latitude, longitude)
} else {
// do something to catch the error
}
} else {
// do the same as above (duplicate code)
}
return coord
}
What I sometimes do is that I use a boolean to keep track of it:
func getCoordinates3(pLatitude: Double?, pLongitude: Double?) -> CLLocationCoordinate2D? {
var coord:CLLocationCoordinate2D?
var success = false
if let latitude = pLatitude {
if let longitude = pLongitude {
coord = CLLocationCoordinate2DMake(latitude, longitude)
success = true
}
}
if !success {
// do something to catch the error
}
return coord
}
Or I use the pattern of exiting early, but I think this is also erroneous
func getCoordinates4(pLatitude: Double?, pLongitude: Double?) -> CLLocationCoordinate2D? {
if let latitude = pLatitude {
if let longitude = pLongitude {
return CLLocationCoordinate2DMake(latitude, longitude)
}
}
// do something to catch the error
return nil
}
Of course this is a striped down example with only two optionals, but when parsing json, a lot more cascading-if might be necessary. I hope the idea and the problem is clear.
As mentioned in the comments, in Swift 1.2 you'll be able to do this:
func getCoordinates2(pLatitude: Double?, pLongitude: Double?) -> CLLocationCoordinate2D? {
var coord:CLLocationCoordinate2D?
if let latitude = pLatitude, longitude = pLongitude {
coord = CLLocationCoordinate2DMake(latitude, longitude)
} else {
// do something to catch the error
}
return coord
}
I would suggest structuring your code to make it easier to switch to that style when it becomes available.
Try this:
func getCoordinates2(pLatitude: Double?, pLongitude: Double?) -> CLLocationCoordinate2D? {
var coord:CLLocationCoordinate2D?
if pLatitude != nil && pLongitude != nil {
let latitude = pLatitude!
let longitude = pLongitude!
coord = CLLocationCoordinate2DMake(latitude, longitude)
} else {
// do something to catch the error
}
return coord
}
This has the same semantics and structure of the Swift 1.2 version, and when that becomes available, you can switch to the newer syntax without having to change any indentation.
Essentially what you are trying to do can be split into two sets of operations:
First one is just plain processing of the data (payload)
And second is handling potential error-condition
Handling of error-condition is always the same, that is, check if previous operation returned an error and pass it upstream, and if so far everything was fine then process the result and pass success further on. This can be nicely encapsulated in a function that takes result and closure as an input and returns yet another result. I think this blog-post from Rob Napier will be quite useful for you.