Convert MKAnnotation array to set - ios

I have a situation where I have map annotations as [MKAnnotation] array in swift. Now I need to convert this into a set for some operation. How can I do this in swift? Basically I need to add only the non existent annotations on the map while updating the map view.

You can find out if an annotation exists on the map by using mapView.view(for:) as mentioned here:
if (self.mapView.view(for: annotation) != nil) {
print("pin already on mapview")
}

"Basically I need to add only the non existent annotations on the map while updating the map view."
We first need to define what makes two annotations equal (in your scenario). Once that is clear you an override the isEqual method. You can then add annotations to a Set.
Here is an example :
class MyAnnotation : NSObject,MKAnnotation{
var coordinate: CLLocationCoordinate2D
var title: String?
convenience init(coord : CLLocationCoordinate2D, title: String) {
self.init()
self.coordinate = coord
self.title = title
}
private override init() {
self.coordinate = CLLocationCoordinate2D(latitude: 0, longitude: 0)
}
override func isEqual(_ object: Any?) -> Bool {
if let annot = object as? MyAnnotation{
// Add your defintion of equality here. i.e what determines if two Annotations are equal.
return annot.coordinate.latitude == coordinate.latitude && annot.coordinate.longitude == coordinate.longitude && annot.title == title
}
return false
}
}
In the code above two instances of MyAnnotation are considered equal when you they have the same coordinates and the same title.
let ann1 = MyAnnotation(coord: CLLocationCoordinate2D(latitude: 20.0, longitude: 30.0), title: "Annot A")
let ann2 = MyAnnotation(coord: CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0), title: "Annot B")
let ann3 = MyAnnotation(coord: CLLocationCoordinate2D(latitude: 20.0, longitude: 30.0), title: "Annot A")
var annSet = Set<MyAnnotation>()
annSet.insert(ann1)
annSet.insert(ann2)
annSet.insert(ann3)
print(annSet.count) // Output : 2 (ann1 & ann3 are equal)

Related

How to Update Annotation Coordinates on MapView

I have a mapView and vehicle coordinates which changes in every 15 seconds so I want to update the coordinates. My current approach is to delete all annotations and adding new ones. However, I can't use animations in that approach, they are just spawning. When I looked google I've found out that people just changing the coordinates of annotations and it's happening. Not for me unfortunately.
Old version:
func updateVehicleLocations(){
let annotations = mapView.annotations.filter {
$0.title != "person"
}
mapView.removeAnnotations(annotations)
for vehicle in vehicles {
let pin = MKPointAnnotation()
pin.coordinate = CLLocationCoordinate2D(latitude: vehicle.latitude, longitude: vehicle.longitude)
pin.title = vehicle.vehicleID
mapView.addAnnotation(pin)
}
if isSetCoordinatesMoreThanOnce { return }
mapView.showAnnotations(mapView.annotations, animated: true)
isSetCoordinatesMoreThanOnce = true
}
My test:
func updateVehicleLocations(){
for annotation in busAnnotations {
UIView.animate(withDuration: 0.5) { [self] in
if let vehicle = self.vehicles.first(where: { $0.vehicleID == annotation.vehicleID }) {
annotation.coordinate = CLLocationCoordinate2D(latitude: vehicle.latitude, longitude: vehicle.longitude)
}
}
}
if isSetCoordinatesMoreThanOnce { return }
for vehicle in vehicles {
let pin = BusAnnotation(coordinate: CLLocationCoordinate2D(latitude: vehicle.latitude, longitude: vehicle.longitude), vehicleID: vehicle.vehicleID)
busAnnotations.append(pin)
}
mapView.addAnnotations(busAnnotations)
mapView.showAnnotations(mapView.annotations, animated: true)
isSetCoordinatesMoreThanOnce = true
}
That was exhausting, I still don't get the logic but here is how I fixed it;
I had to use a custom class(of MKAnnotation) to set coordinates otherwise the coordinate property is read-only. So, here is an example custom annotation class;
final class BusAnnotation: NSObject, MKAnnotation{
var vehicleID: String
var coordinate: CLLocationCoordinate2D
init(coordinate: CLLocationCoordinate2D, vehicleID:String) {
self.coordinate = coordinate
self.vehicleID = vehicleID
super.init()
}
}
however, it doesn't update coordinates, to fix it we need to add "dynamic" keyword to coordinate property everything works fine!
final class BusAnnotation: NSObject, MKAnnotation{
var vehicleID: String
dynamic var coordinate: CLLocationCoordinate2D
init(coordinate: CLLocationCoordinate2D, vehicleID:String) {
self.coordinate = coordinate
self.vehicleID = vehicleID
super.init()
}
}

Is there a preset 'index' or something similar that allows me to refer to specific annotations that a user created? Swift 4

I have an app where the user can search for locations, tap on the row in the table view, and an annotation will be placed at the placemark. I have a button, "meetUpButton" that allows them to put multiple annotations, rather than what my code does by default which is remove the annotation if a new search result is clicked. Is there a way to refer to a specific annotation that my user created, even if they made multiple? For example, say I want to add the latitude of two annotations that my user created...
If I have
let annotation = MKPointAnnotation()
annotation.coordinate.latitude
Is there a way to refer to something like the first annotation with an index of 0 and the next one that my user chose with a 1 or another way? Here's some of my code which might make this clearer.
extension ViewController: handleMapSearch {
func dropPinZoomIn(placemark:MKPlacemark) {
resultSearchController?.isActive = false
// cache the pin
selectedPin = placemark
// clear existing pins
if meetUpOutlet.isEnabled == true {
mapView.removeAnnotations(mapView.annotations)
} else {
}
let annotation = MKPointAnnotation()
annotation.coordinate = placemark.coordinate
annotation.title = placemark.name
if let city = placemark.locality,
let state = placemark.administrativeArea {
annotation.subtitle = "(\(city)) (\(state))"
}
mapView.addAnnotation(annotation)
let span = MKCoordinateSpanMake(0.05, 0.05)
let region = MKCoordinateRegionMake(placemark.coordinate, span)
mapView.setRegion(region, animated: true)
}
}
You can try to use annotations property of MKMapView instance , to get all use
let anns = self.mapView.annotations
You can implement Custom annotation class.
e.g.
class MyAnnotation : NSObject, MKAnnotation {
var coordinate : CLLocationCoordinate2D
var title: String?
var subtitle: String?
var index: Int?
init(location coord:CLLocationCoordinate2D) {
self.coordinate = coord
super.init()
}
}
Usage:
let annotation = MyAnnotation(location: coordinate)
annotation.title = "title"
annotation.subtitle = "subtitle"
annotation.index = 1
Remove annotations (only index == 1)
for annotation in self.mapView.annotations {
guard let annotation = annotation as? MyAnnotation else {
continue
}
if annotation.index == 1 {
self.mapView.removeAnnotation(annotation)
}
}

MKAnnotation subclass issues

I upgraded to Swift 1.2 last night, and I got a bug I really can't figure out. The below code worked fine in the previous version of Xcode and Swift.
//MARK: Annotation Object
class PointAnnotation : NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var title: String
var subtitle: String
var point: Point
var image: UIImage
var md: String
init(point: Point) {
self.coordinate = point.coordinate
self.title = point.title
self.subtitle = point.teaser
self.image = UIImage(named: "annotation.png")!
self.point = point
self.md = point.content
}
}
On line 3, I get the somewhat hard to understand error
Objective-C method 'setCoordinate:' provided by the setter for 'coordinate' conflicts with the optional requirement method 'setCoordinate' in protocol 'MKAnnotation' I tried changing variable names and such, but no help. Does anyone have any idea how to fix this?
The class is for annotations on my mapview.
If you do not require to change coordinates after initialization then you can use it that way. It works for me with Swift 1.2:
class CustomAnnotation : NSObject, MKAnnotation {
let coordinate: CLLocationCoordinate2D
var title: String
init(coordinate: CLLocationCoordinate2D, title: String) {
self.coordinate = coordinate
self.title = title
}
}
You are overriding a readonly ivar in MKAnnotation with a readwrite.
var coordinate: CLLocationCoordinate2D { get }
I had the same issue so I just did this:
private var _coordinate: CLLocationCoordinate2D
var coordinate: CLLocationCoordinate2D {
return _coordinate
}
init(coordinate: CLLocationCoordinate2D) {
self._coordinate = coordinate
super.init()
}
func setCoordinate(newCoordinate: CLLocationCoordinate2D) {
self._coordinate = newCoordinate
}
That way coordinate is still readonly and you can use the setCoordinate method.

Swift optional variable not set using Xcode 6.1

I'm trying to build my first Swift application. In this application I'm looping over an KML file that contains information about some restaurant and for each one of them I'm trying to build a Place object with the available information, compare a distance and keep the Place which is the closest to a given point.
Here is my Place model, a very simple model (Place.swift):
import Foundation
import MapKit
class Place {
var name:String
var description:String? = nil
var location:CLLocationCoordinate2D = CLLocationCoordinate2D(latitude:0, longitude:0)
init(name: String, description: String?, latitude: Double, longitude: Double)
{
self.name = name
self.description = description
self.location = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
}
func getDistance(point: CLLocationCoordinate2D) -> Float
{
return Geo.distance(point, coordTo: self.location)
}
}
and here is the part of the application that is looping over the items from the KML file.
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) {(response, data, error) in
let xml = SWXMLHash.parse(data);
var minDistance:Float = Float(UInt64.max)
var closestPlace:Place? = nil
var place:Place? = nil
for placemark in xml["kml"]["Document"]["Folder"]["Placemark"] {
var coord = placemark["Point"]["coordinates"].element?.text?.componentsSeparatedByString(",")
// Create a place object if the place has a name
if let placeName = placemark["name"].element?.text {
NSLog("Place name defined, object created")
// Overwrite the place variable with a new object
place = Place(name: placeName, description: placemark["description"].element?.text, latitude: (coord![1] as NSString).doubleValue, longitude: (coord![0] as NSString).doubleValue)
var distance = place!.getDistance(self.middlePosition)
if distance < minDistance {
minDistance = distance
closestPlace = place
} else {
NSLog("Place name could not be found, skipped")
}
}
}
I added breakpoints in this script, when the distance is calculated. The value of the place variable is nil and I don't understand why. If I replace this line:
place = Place(name: placeName, description: placemark["description"].element?.text, latitude: (coord![1] as NSString).doubleValue, longitude: (coord![0] as NSString).doubleValue)
by this line:
let place = Place(name: placeName, description: placemark["description"].element?.text, latitude: (coord![1] as NSString).doubleValue, longitude: (coord![0] as NSString).doubleValue)
I can see that my place object is instantiated correctly now but I can't understand why.
Also I have the exact same issue when I tried to save the closest place:
closestPlace = place
In the inspector the value of closestPlace is nil even after being set with my place object.
I fixed my issue adding a ! after the Place object. I guess is to tell that I am sure I have an Object in this variable and that is not nil.
if let placeName = placemark["name"].element?.text {
NSLog("Place name defined, object created")
// Overwrite the place variable with a new object
place = Place(name: placeName, description: placemark["description"].element?.text, latitude: (coord![1] as NSString).doubleValue, longitude: (coord![0] as NSString).doubleValue)
var distance = place!.getDistance(self.middlePosition)
if distance < minDistance {
minDistance = distance
closestPlace = place!
} else {
NSLog("Place name could not be found, skipped")
}
}

MKPinAnnotation to Detailed view

I have an object array from a PFQuery and then add MKPinAnnotations in my mapView with the following function:
func addAnnotation() {
for (var i = 0; i < self.objectArray.count; i++) {
var location = object.objectForKey("location") as PFGeoPoint
var annotation = MKPointAnnotation()
annotation.setCoordinate(CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude))
annotation.title = object.objectForKey("title") as NSString
var priceFormatter = NSNumberFormatter()
self.mapView.addAnnotation(annotation)
}
}
I have want to be able to click on the pin (or the information tab that shows when the pin is tapped) and segue to a detail view controller and pass the information associated with that pin or that index place in the Array...
I only need to know how to get the index number of the tapped pin so I can perform the segue...I've been stuck in this piece of code for the past 3 days and gave up and decided to ask the masters :P
Hope you can help me out please.
Thanks
Create a custom class for your annotation where you can store additional information such as the array index:
class PinAnnotation : NSObject, MKAnnotation {
private var coord: CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 0, longitude: 0)
var coordinate: CLLocationCoordinate2D {
get {
return coord
}
}
var title: String = ""
var subtitle: String = ""
var index: Int = 0
func setCoordinate(newCoordinate: CLLocationCoordinate2D) {
self.coord = newCoordinate
}
}
later in your code you can set the array index:
func addAnnotation() {
for (var i = 0; i < self.objectArray.count; i++) {
var location = object.objectForKey("location") as PFGeoPoint
var annotation = PinAnnotation()
annotation.setCoordinate(CLLocationCoordinate2D(latitude: location.latitude, longitude: location.longitude))
annotation.title = object.objectForKey("title") as NSString
annotation.index = i
var priceFormatter = NSNumberFormatter()
self.mapView.addAnnotation(annotation)
}
}
You have access to all the properties when you are working with the annotation object

Resources