So here is my imageFor Mapbox function:
func mapView(_ mapView: MGLMapView, imageFor annotation: MGLAnnotation) -> MGLAnnotationImage? {
var reuseid = ""
switch annotation.subtitle ?? "" {
case "uno":
reuseid = "uno"
case "dos":
reuseid = "dos"
case "tres":
reuseid = "tres"
default:
reuseid = "default"
}
var annotationImage = mapView.dequeueReusableAnnotationImage(withIdentifier: reuseid)
if annotationImage == nil {
guard let image = UIImage(named: reuseid) else { return nil }
annotationImage = MGLAnnotationImage(image: image, reuseIdentifier: reuseid)
let tapGesture = AnnotationTapGestureRecognizer(target: self, action: #selector(Coordinator.tappedAnnotation(sender:)))
tapGesture.annotation = annotation as! MGLPointAnnotation
annotationImage.addGestureRecognizer(tapGesture) //error on this line says 'Value of type 'MGLAnnotationImage?' has no member 'addGestureRecognizer''
}
return annotationImage
}
class AnnotationTapGestureRecognizer: UITapGestureRecognizer {
var annotation = MGLPointAnnotation()
}
How do I make it so that I can add a gesture recognizer to the annotationImage I'm returning?
Instead of returning an MGLAnnotationImage
, return an MGLAnnotationView
, which is just a subclass of UIView
. You can attach a gesture recognizer to that.
To simplify things let's subclass MGLAnnotationView
and create our custom marker. This marker is nothing fancy, just an image (50 x 50 points) that of course is tappable with our custom recognizer. To do that I just effectively made the marker a UIButton
. When the user taps on this marker, the marker will call the didSelectMarker(point:)
method on the delegate.
class XAnnotationView: MGLAnnotationView {
weak var delegate: XMapMarkerDelegate?
var point: MGLPointAnnotation?
required override init(reuseIdentifier: String?) {
super.init(reuseIdentifier: reuseIdentifier)
let rect = CGRect(x: 0, y: 0, width: 50, height: 50)
let button = UIButton(frame: rect)
let image = UIImage(named: "mapMarker")
button.setImage(image, for: .normal)
button.setImage(image, for: .selected)
button.setImage(image, for: .highlighted)
button.imageView?.contentMode = .scaleAspectFit
button.addTarget(self, action: #selector(markerAction), for: .touchUpInside)
frame = rect
addSubview(button)
isEnabled = false // disable it if we're adding our own gesture recognizer
}
required init?(coder: NSCoder) {
return nil
}
@objc private func markerAction() {
delegate?.didSelectMarker(point: point)
}
}
The XMapMarkerDelegate
delegate is something we just made up so let's define it by creating a protocol that any class object can conform to, likely a UIViewController
, to handle this method. Any view controller that conforms to this protocol can now handle these tap events.
protocol XMapMarkerDelegate: AnyObject {
func didSelectMarker(point: MGLPointAnnotation)
}
Then whichever class object is displaying our map, likely a UIViewController
, conform it to our custom protocol and handle the touch event. This object will likely be the same object that is the map's delegate, so let's just group them together for neater organization:
extension SomeViewController: MGLMapViewDelegate, XMapMarkerDelegate {
/* This is one of Mapbox's many delegate methods.
This method is for adding annotation views to the map. */
func mapView(_ mapView: MGLMapView, viewFor annotation: MGLAnnotation) -> MGLAnnotationView? {
if annotation is MGLPointAnnotation { // only handle point annotations
if let reusable = dequeueReusableAnnotationView(withIdentifier: "marker") as? XAnnotationView { // find a reusable marker if one is available with the given identifier
reusable.point = annotation // assign this marker the current point
return reusable
} else { // if no reusable marker found, create a new marker with the given identifier
let new = XAnnotationView(reuseIdentifier: "marker")
new.delegate = self // assign self as the delegate
new.point = annotation // assign this marker the current point
return new
}
} else {
return nil
}
}
/* This is our custom delegate method. */
func didSelectMarker(point: MGLPointAnnotation) {
print("did tap marker")
}
}
You can customize this as much as you want to add custom data or objects to the marker. You can create multiple subclasses of the annotation view, each with a different size or image, and use different ones on the map depending on the annotation point. The possibilities are almost limitless.