-1

I have a short line (MKPolyline) and a custom annotation class (MKPointAnnotaion). Right now I have the point annotation located at the midpoint of the polyline. However, I would like the callout to be displayed whenever any point on the polyline is touched, similar to how routing performs in Maps. Since polylines don't seem to have a touchable attribute like annotations, is there a way to have the annotation encompass the entire line?

I do not care about the annotations marker (it is actually a nuisance and would prefer to hide it), only the callout and associated class info.

If at all possible could someone provide a brief example code with the answer?

EDIT: Other posts seem to be from 5+ years ago with links that do not work anymore

DSmith
  • 159
  • 1
  • 1
  • 13
  • I have actually done this a while back before Swift came out. I will find the respective project and see if I can pull out the relevant bits and use them in a quick Swift project first before posting. – pnizzle Nov 07 '19 at 08:39

1 Answers1

2

A couple of thoughts:

  1. I think your “add annotation” approach for where you want this callout to start from is going to be the easiest approach. If you start contemplating making an annotation view subclass that shows the path, I think that’s going to get pretty hairy pretty quickly (handling scaling changes, rotations, 3D, etc.). Overlays give you a bunch of behaviors for free, so I think you’ll want to stick with that. And annotations give you callout behaviors for free, too, so I think you’ll want to stay with that too.

  2. If you don’t want your annotation view to show a pin/marker, just don’t subclass from MKPinAnnotationView or MKMarkerAnnotationView, but rather just use MKAnnotationView.

  3. The tricky step is how to detect taps on the MKPolylineRenderer. One idea is to create a path that traces the outline of the path of the MKPolyline.

    extension MKPolyline {
        func contains(point: CGPoint, strokeWidth: CGFloat = 44, in mapView: MKMapView) -> Bool {
            guard pointCount > 1 else { return false }
    
            let path = UIBezierPath()
            path.move(to: mapView.convert(from: points()[0]))
            for i in 1 ..< pointCount {
                path.addLine(to: mapView.convert(from: points()[i]))
            }
            let outline = path.cgPath.copy(strokingWithWidth: strokeWidth, lineCap: .round, lineJoin: .round, miterLimit: 0)
            return outline.contains(point)
        }
    }
    

    where

    extension MKMapView {
        func convert(from mapPoint: MKMapPoint) -> CGPoint {
            return convert(mapPoint.coordinate, toPointTo: self)
        }            
    }
    

You can then create a gesture recognizer that detects a tap, checks to see if it’s within this path that outlines the MKPolyline, or whatever. But these are the basic pieces of the solution.

Obviously, the answers here outline different, apparently looking at the distance to the paths. That conceivably would work, too.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Thank you for the answer! Just for clarification, when a tap was detected, I would convert the point it happened at using the convert(), then check if it was in the polyline using contains()? – DSmith Apr 18 '19 at 14:40
  • Yes. Or, obviously, if your tap gesture recognizer happened to be on the map view itself, it’s already in the right coordinate system. – Rob Apr 18 '19 at 17:43
  • Not sure if this was covered, but once the tap is detected and determined to be inside the line, how would I get the annotation callout to react? – DSmith Apr 18 '19 at 22:11