MKAnnotation subclass issues - ios

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.

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

Convert MKAnnotation array to set

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)

iOS - swift 3 - heatmap

i am using this project to generate my heatmap:
https://github.com/dataminr/DTMHeatmap
I integrated the code as stated in:
https://github.com/dataminr/DTMHeatmap/issues/1
from #johndpope:
https://github.com/johndpope/TranSafe
First, it is compiled successfully, but when i use the "readData" like this:
readData([[52.517138, 13.401489], [52.517137, 13.401488], [52.517136, 13.401487]])
i get the
Thread 1: EXC_BAD_ACCESS(code=2, address=blabla) error
here is the method:
func readData(_ array: [[Double]]){
self.heatmap = DTMHeatmap()
var dict = Dictionary<NSObject, AnyObject>();
for entry in array{
let coordinate = CLLocationCoordinate2D(latitude: entry[1], longitude: entry[0]);
let mapPoint = MKMapPointForCoordinate(coordinate)
let type = NSValue(mkCoordinate: coordinate).objCType // <- THIS IS IT
let value = NSValue(bytes: Unmanaged.passUnretained(mapPoint as AnyObject).toOpaque(), objCType: type);
dict[value] = 1 as AnyObject?;
}
self.heatmap.setData(dict as [AnyHashable: Any]);
self.mapView.add(self.heatmap)
}
func MKMapPointForCoordinate(_ coordinate: CLLocationCoordinate2D) -> MKMapPoint {
return MKMapPointForCoordinate(coordinate);
}
// etc ...
I have really no idea what i have done wrong, anybody could help me with this issue?
As far as I can read from the original code of DTMHeatmap, the keys for the dictionary passed for setData need to be NSValues containing MKMapPoint. And the code you have shown is not a proper code to make such NSValues. (I really doubt the original Swift 2 code you have found would actually work..., MKMapView cannot be bridged to Objective-C object in Swift 2, so mapPoint as! AnyObject should always fail.)
The readData method should be something like this:
func readData(_ array: [[Double]]){
self.heatmap = DTMHeatmap()
var dict: [AnyHashable: Any] = [:]
for entry in array{
let coordinate = CLLocationCoordinate2D(latitude: entry[1], longitude: entry[0]);
var mapPoint = MKMapPointForCoordinate(coordinate)
//Creating `objCType` manually is not recommended, but Swift does not have `#encoding()`...
let type = "{MKMapPoint=dd}"
let value = NSValue(bytes: &mapPoint, objCType: type)
dict[value] = 1
}
self.heatmap.setData(dict)
self.mapView.add(self.heatmap)
}
(I haven't checked with the actual DTMHeatmap, so you may need some more fixes.)
Very performant heatmap library. I managed to get this working and it renders nicely. I am adding this answer because it also includes the rendererFor overlay delegate function needed to work.
class HeatmapViewController: UIViewController, MKMapViewDelegate {
var heatmap: DTMHeatmap? = nil
var diffHeatmap: DTMDiffHeatmap? = nil
#IBOutlet weak var mapView: MKMapView!
override func viewDidLoad() {
super.viewDidLoad()
var ret: [AnyHashable: Any] = [:]
for location in locations {
var mapPoint = MKMapPointForCoordinate(location.coordinate)
let mapPointValue = NSValue(bytes: &mapPoint, objCType: "{MKMapPoint=dd}")
ret[mapPointValue] = 10.0 // weight
}
self.heatmap = DTMHeatmap()
self.heatmap?.setData(ret)
self.mapView.delegate = self; // Important
self.mapView.add(self.heatmap!)
}
}
This part is very important and for this to work you need to set the mapView's delegate to self.
func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {
return DTMHeatmapRenderer(overlay: overlay)
}

Custom MKAnnotation disappears when I alter it's coordinate

I don't exactly know if this has been answered before, but I have not been able to find it so I thought I would post a question.
I have a custom MKAnnotation that shows up perfectly fine if i use coordinates such as : "74.8044595495237 -21.7716836352598"
var currLat: Double! = 74.8044595495237
var currLong: Double! = -21.7716836352598
let egg: EggPin = EggPin(coordinate: CLLocationCoordinate2DMake(currLat, currLong), title: "Egg", subtitle: "This is an egg")
That works just fine. The custom MKAnnotation shows up on the map and everything is fine and dandy. But, when I change the coordinates of the EggPin by subtracting 1.0 from currLat and currLong like so:
var currLat: Double! = 74.8044595495237
var currLong: Double! = -21.7716836352598
let egg: EggPin = EggPin(coordinate: CLLocationCoordinate2DMake(currLat - 1.0, currLong - 1.0), title: "Egg", subtitle: "This is an egg")
The MKAnnotation disappears. I am new to MapKit so I feel like I must be missing something very simple about setting coordinates. Below is my EggPin Class if that will help :
import Foundation
import MapKit
class EggPin: NSObject, MKAnnotation {
var coordinate: CLLocationCoordinate2D
var title: String?
var subtitle: String?
init(coordinate: CLLocationCoordinate2D, title: String, subtitle: String) {
self.coordinate = coordinate
self.title = title
self.subtitle = subtitle
super.init()
}
}
The problem was that the MKAnnoation went way off the screen when I subtracted 1.0 from it, but subtracting .005 worked fine as it was shown by the pin.

Tap MKAnnotationView with XCTestCase

I want to tap MKAnnotationView using my XCTestCase with UITest, so I have my mapView as a XCUIElement. I have no idea how to get annotations from mapView, I don't want to put fixed positions on this on the screens because it is tested on various devices. So I'm stuck on the
let mapView:XCUIElement = app.maps.elementBoundByIndex(0)
Any ideas?
To interact with a map annotation use otherElements referenced by the title attribute.
For example, in your production code set a title in your MKAnnotation.
class Annotation: NSObject, MKAnnotation {
let coordinate: CLLocationCoordinate2D
let title: String?
init(title: String?, coordinate: CLLocationCoordinate2D) {
self.title = title
self.coordinate = coordinate
}
}
let coordinate = CLLocationCoordinate2DMake(40.703490, -73.987770)
let annotation = Annotation(title: "BeerMenus HQ", coordinate: coordinate)
let mapView = MKMapView()
mapView.addAnnotation(annotation)
Then under test you can reference the annotation via it's title attribute.
let app = XCUIApplication()
let annotation = app.maps.element.otherElements["BeerMenus HQ"]
annotation.tap()

Resources