I've had some past experience using MKMapView
and MKPointAnnotation
, which I used to put some pin on a map.
This time I am trying to go one step further and use MKPinAnnotationView
, to write a label along with some of the pins.
Unfortunately, it doesn't all work as I expect.
Here is what I want to do:
I have a map (an MKMapView object) and when I touch it, I put a pin at the touch point, then some computation is performed and this gives me a second point on the map. I put a second pin on the map (located at the second point), on this last pin I want to put a label, say "Hello Second!", but this label needs to be updated when the pin changes place.
Here is the relevant code:
class ViewController: UIViewController, MKMapViewDelegate {
var mapView:MKMapView!, touchPoint,secondPoint:MKPointAnnotation!
override func viewDidLoad() {
super.viewDidLoad()
mapView = MKMapView()
...........
let mapTap = UITapGestureRecognizer(target: self,
action: #selector(ViewController.mapTouchHandler))
mapView.addGestureRecognizer(mapTap)
}
func mapTouchHandler(gesture:UITapGestureRecognizer) {
...........
// Compute map coordinates for the touch point (tapGeoPoint).
if touchPoint == nil {
touchPoint = MKPointAnnotation()
mapView.addAnnotation(touchPoint);
}
touchPoint.coordinate = CLLocationCoordinate2D(latitude: tapGeoPoint.latitude,
longitude: tapGeoPoint.longitude)
...........
computeSecondPoint(url: someComputedURL)
}
func computeSecondPoint(url searchURL:String) {
let reqURL = NSURL(string: searchURL)!, session = URLSession.shared,
task = session.dataTask(with: reqURL as URL) {
(data: Data?, response: URLResponse?, error: Error?) in
if error == nil {
do {let allData = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSArray
.................
// Compute map coordinates for the second point (secondPointCoord).
if self.secondPoint == nil {
self.secondPoint = MKPointAnnotation()
self.mapView.addAnnotation(self.secondPoint)
}
DispatchQueue.main.async {
() -> Void in
self.secondPoint.coordinate = CLLocationCoordinate2D(latitude: secondPointCoord.latitude,
longitude: secondPointCoord.longitude)
self.secondPoint.title = "Hello Second -TITLE!"
//* I want to update the label for this pin (attached to the secondPoint) too.
}
} catch let error as NSError {print(error.localizedDescription)}
} else {
print("Error inside \(#function):\n\(error)")
}
}
task.resume()
}
func mapView(_ mapView: MKMapView,
viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "pin"
var view: MyPinAnnotationView
if let dequeuedView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)
as? MyPinAnnotationView {
dequeuedView.annotation = annotation
view = dequeuedView
} else {
view = MyPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
if ((annotation.coordinate.latitude != touchPoint.coordinate.latitude) ||
(annotation.coordinate.longitude != touchPoint.coordinate.longitude)) {//* I need a better test to check that this not touchPoint!
view.pinTintColor = UIColor.blue
view.canShowCallout = true
view.calloutOffset = CGPoint(x: -7, y: 0)
view.setInfo(title: "Hi Start Label!")
} else {
view.pinTintColor = UIColor.red
view.canShowCallout = false
}
}
return view
}
}
Here is the class MyPinAnnotationView:
import UIKit
import MapKit
class MyPinAnnotationView: MKPinAnnotationView {
let information:UILabel = UILabel(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 70.0, height: 30.0)))
func setInfo(title : String)
{
information.text = title
information.textAlignment = .center
self.addSubview(information)
}
func hideInfo() {
information.removeFromSuperview()
}
}
The lines with a comment marked //*, show where I need some help.
First issue, I want to update the label on the secondPoint, but I don't know what code to use. Line:
//* I want to update the label for this pin (attached to the secondPoint) too.
Now the label appears as first set, but I don't know how to update it.
Second issue, there must be a better way to test which pin I am dealing with. Line:
//* I need a better test to check that this not touchPoint!
If you want the annotation view to show some text that is updated as the annotation changes, use KVO on the annotation. So, first, create a model object, the annotation, which includes the new property to be observed:
I've called this new property
information
, but you can probably come up with a better name that captures the functional intent of this property. But hopefully it illustrates the idea. The key takeaway here is that if this property may change at a later point, we'll want to make itdynamic
so we can use KVO to observe those changes.I've changed the name of the label to
informationLabel
to make it a little more explicit that it is a view, not to be confused with the model property,information
, of our newMyAnnotation
class. Also, I'd suggest more meaningful class names thanMyAnnotation
andMyPinAnnotationView
, perhaps using some name that better captures the functional intent of these two classes.Regardless, as you can see, when you set the
annotation
for this annotation view, it updates the labeltext
. But it also observes the annotation's newinformation
property via KVO, so if this property changes later, the view will update accordingly.