Update annotations loaded with http - swift

I am loading some GeoData from my server and want to display them throw annotations:
Alamofire.request("http://localhost:1234/app/data").responseJSON { response in
switch response.result {
case .success(let value):
let json = JSON(value)
var annotations = [Station]()
for (key, subJson):(String, JSON) in json {
let lat: CLLocationDegrees = subJson["latitude"].double! as CLLocationDegrees
let long: CLLocationDegrees = subJson["longtitude"].double! as CLLocationDegrees
self.annotations += [Station(name: "test", lat: lat, long: long)]
}
DispatchQueue.main.async {
let allAnnotations = self.mapView.annotations
self.mapView.removeAnnotations(allAnnotations)
self.mapView.addAnnotations(annotations)
}
case .failure(let error):
print(error)
}
}
and my Station class:
class Station: NSObject, MKAnnotation {
var identifier = "test"
var title: String?
var coordinate: CLLocationCoordinate2D
init(name:String, lat:CLLocationDegrees, long:CLLocationDegrees) {
title = name
coordinate = CLLocationCoordinate2DMake(lat, long)
}
}
So what I did, is basically:
Load data from remote service and display these data as annotations on MKMapView.
But: somehow these annotations are not loaded on the map, even i first "remove" and next "add" them.
Any suggestions?

You're adding your Station instances to some self.annotations property, not your local variable, annotations. Thus, annotations local var is still just that empty array.
Obviously, you can fix that by referencing annotations rather than self.annotations:
var annotations = [Station]()
for (key, subJson): (String, JSON) in json {
let lat = ...
let long = ...
annotations += [Station(name: "test", lat: lat, long: long)] // not `self.annotations`
}
Or you can use map, avoiding this potential confusion altogether:
let annotations = json.map { key, subJson -> Station in
Station(name: "test", lat: subJson["latitude"].double!, long: subJson["longitude"].double!)
}

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.

Unable to get the data from a nested json swift

I'm learning swift.
I have a json from server.
[
{
"dId": 1,
"vendor": {
"vendorId": 1,
"name": "Gems",
"about": "Get Good quality stones",
"address": "JacksonVille Road",
"latitude": 12.232323,
"longitude": 77.230802,
},
"name": "Gems Delight",
}
]
I'm unable to parse this json and get the data from lat and lon and name from vendor.
My url method and my for loop how can I exactly get the latitude and longitude from the loop and put them on the map ?
My View did load method
override func viewDidLoad() {
super.viewDidLoad()
guard let gitUrl = URL(string: "localhost:8080/deals") else { return }
URLSession.shared.dataTask(with: gitUrl) { (data, response
, error) in
guard let data = data else { return }
do {
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)
var lat:Double
var lon:Double
var nam = ""
for items in json as! [AnyObject]{
// let te = type(of: items)
// print(te)
let new = items["vendor"]
for (it,key) in new as! [String:Any]{
// print(it,key)
// print(it["longitude"])
if it == "longitude"{
print(it,key)
lon = key as! Double
}
if it == "latitude"{
print(it,key)
lat = key as! Double
}
if it == "name"{
nam = key as! String
}
if (nam.isEmpty == false){
print("falsdalsdasldasd")
self.locationManager.delegate = self
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
let camera = GMSCameraPosition.camera(withLatitude:lat, longitude:lon, zoom: 4.0)
let subView = GMSMapView.map(withFrame: self.view.bounds,camera:camera)
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude:lat, longitude:lon)
marker.title = nam
// marker.snippet = "Australia"
marker.map = subView
self.mapView.addSubview(subView)
}
// print(it["longitude"])
}
// let te = type(of: items)
// for it in new as![AnyObject]{
//// print(it"])
// print(it)
//
// }
// print(items["vendor"].["latitude"])
// print(items.vendor)
// print(items[""])
// let nam = items["name"];
// let desc = items["description"];
// self.locationNames.append(nam as! String)
// self.locationDescription.append(desc as! String)
//
}
// self.colecVw.delegate = self
// self.colecVw.dataSource = self
// self.colecVw.reloadData()
// }
} catch let err {
print("Err", err)
}
}.resume()
print("coming here")
// Create a GMSCameraPosition that tells the map to display the
// coordinate -33.86,151.20 at zoom level 6.
// let mapView = GMSMapView(frame: CGRect(x: 0, y: 64, width: self.currentDeviceSize.width, height: self.bottomBgView.frame.minY - 64))
// view = mapView
//
// // Creates a marker in the center of the map.
// let marker = GMSMarker()
// marker.position = CLLocationCoordinate2D(latitude: -33.86, longitude: 151.20)
// marker.title = "Sydney"
// marker.snippet = "Australia"
// marker.map = mapView
}
Please help me as I'm learning to code my code might not be correct can please dont mind and load the data of lat and long onto the map. Should I construct an object and put them there ?
Variable 'lon' used before being initialized
Variable 'lat' used before being initialized
This is the error I get from the code.
The errors occur because lat and lon must have a value in the line
let camera = GMSCameraPosition.camera(withLatitude:lat, longitude:lon, zoom: 4.0)
which is not guaranteed if the key comparisons fail.
The condition is fulfilled if you write
var lat = 0.0 // The type Double is inferred
var lon = 0.0
But rather than enumerating the dictionary get the values for the keys directly and safely with optional binding
do {
// no mutableContainers !!
let json = try JSONSerialization.jsonObject(with: data) as! [[String:Any]]
for item in json {
if let vendor = item["vendor"] as? [String:Any],
let lat = vendor["latitude"] as? Double,
let lon = vendor["longitude"] as? Double,
let name = item["name"] as? String, !name.isEmpty {
print("falsdalsdasldasd")
// self.locationManager.delegate = self
// self.locationManager.requestWhenInUseAuthorization()
// self.locationManager.startUpdatingLocation()
let camera = GMSCameraPosition.camera(withLatitude:lat, longitude:lon, zoom: 4.0)
let subView = GMSMapView.map(withFrame: self.view.bounds,camera:camera)
let marker = GMSMarker()
marker.position = CLLocationCoordinate2D(latitude:lat, longitude:lon)
marker.title = name
marker.map = subView
self.mapView.addSubview(subView)
}
}
} catch {
print("Err", error)
}
And it's nonsensical to call the LocationManager methods in each iteration. Call them once at the beginning of viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
self.locationManager.delegate = self
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
...
You can try
struct Root: Codable {
let dID: Int
let vendor: Vendor
let name: String
enum CodingKeys: String, CodingKey {
case dID = "dId"
case vendor, name
}
}
struct Vendor: Codable {
let vendorID: Int
let name, about, address: String
let latitude, longitude: Double
enum CodingKeys: String, CodingKey {
case vendorID = "vendorId"
case name, about, address, latitude, longitude
}
}
--
let arr = try? JSONDecoder().decode([Root].self, from:data)
print(arr?.forEach {$0.vendor.latitude })
See this one
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers)
Pass json to
if let data = json as? NSArray {
for data in data {
if let data = data as? [String: AnyObject] {
let dataID = data["dId"] as? Int
if let data = data[“vendor”] as? [String: AnyObject] {
let vendorID = data["vendorId"] as? Int
}
}
}
}

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 found nil when Firebase DB persistence is enabled

In my app I am using Firebase db to store my data, I have everything working as it should, with my tableview populating with the data from the db. However I have found that on initial start it is quite slow to download the data, so I enable persistence in AppDelegate. When I did this and ran the app it crashes on load with an unexpectedly found nil error.
FIRDatabase.database().persistenceEnabled = true
is the line I added to AppDelegate under didFinishLaunching section.
The error occurs in this class on the line I have marked
import Foundation
import FirebaseDatabase
import CoreLocation
struct newTracks {
//Declerations
var locationManager = CLLocationManager()
let name: String!
let lat: Double!
let lon: Double!
let countryImage: String!
let link: String!
let ref: FIRDatabaseReference?
let distance: Double
//Initialize
init(name: String, trackId: Int, postcode: String, trackType: String, trackURL: String, locID: Int, lat: Double, lon: Double, phoneNumber: String, email: String, rating: Double, numrating: Double, totalrating: Double, countryImage: String, link: String, distance: Double) {
self.name = name
self.ref = nil
self.lat = lat
self.lon = lon
self.countryImage = countryImage
self.link = link
self.distance = distance
}
//Initialize data from Firebase
init(snapshot: FIRDataSnapshot) {
let snapshotValue = snapshot.value as! [String: AnyObject]
name = snapshotValue["name"] as! String
lat = snapshotValue["lat"]as! Double
lon = snapshotValue["long"]as! Double
ref = snapshot.ref
countryImage = snapshotValue["country"] as! String <-- Unexpectedly found nil
link = snapshotValue["link"] as! String
let currentLat = self.locationManager.location!.coordinate.latitude
let currentLon = self.locationManager.location!.coordinate.longitude
let myLocation = CLLocation(latitude: currentLat, longitude: currentLon)
let loc = CLLocation(latitude: lat, longitude: lon)
let distanceInMiles = round(myLocation.distance(from: loc) / 1609.34)
distance = distanceInMiles
}
func toAnyObject() -> Any {
return [
"name": name,
"lat": lat,
"lon": lon,
"countryImage": countryImage,
"link": link,
"distance": distance
]
}
}
I don't get why this works ok when persistence isn't switched on, but when I enable it it crashes. Any help is much appreciated.
You have marked countryImage as Optional value. Your snapshot may not have value for "country".
Try to change:
let countryImage: String!
to:
let countryImage: String
EDIT:
Maybe, when you are turning persistance on, your application is taking old data from cache. And this data doesn't have ["country"] in snapshot value. So it founds nil -> throwing error. Check your snapshotValue through breakpoint. And check if it has value for country
Hope it helps
I have resolved this by uninstalling the app from my phone, this cleared away the old cached data, seems to load ok now

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.