1

I'm drawing overlays based on directions. I draw from route a to route b and then route b to route c.

I would like to detect if the overlay is tapped anywhere on mkoverlay.

I used this example Detecting touches on MKOverlay in iOS7 (MKOverlayRenderer)

and converted it to swift.

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {

    if let touch = touches.first {
        if touch.tapCount == 1 {
            let touchLocation = touch.location(in: self)
            let locationCoordinate = self.convert(touchLocation, toCoordinateFrom: self)

            let mapPoint: MKMapPoint = MKMapPointForCoordinate(locationCoordinate)
            let mapPointAsCGP = CGPoint(x: CGFloat(mapPoint.x), y: CGFloat(mapPoint.y))
            for overlay: MKOverlay in self.overlays {
                if (overlay is MKPolygon) {
                    let polygon: MKPolygon? = (overlay as? MKPolygon)
                    let mpr: CGMutablePath = CGMutablePath()
                    let polygonPoints = polygon?.points
                    let polygonPointCount = Int((polygon?.pointCount)!)
                    for p in 0..<polygonPointCount {
                        let mp: MKMapPoint = polygonPoints.unsafelyUnwrapped()[polygonPointCount]
                        if p == 0 {
                            mpr.move(to: CGPoint(x: CGFloat(mp.x), y: CGFloat(mp.y)), transform: .identity)
                        }
                        else {
                            mpr.addLine(to: CGPoint(x: CGFloat(mp.x), y: CGFloat(mp.y)), transform: .identity)
                        }
                    }

                    if mpr.contains(mapPointAsCGP, using: .winding, transform: .identity) {
                        print("test")
                    }
                }
            }

        }
    }

    super.touchesEnded(touches, with: event)
}

I'm not sure why they have the if statement there

if (overlay is MKPolygon)  

because this would never be called since its an array of mkoverlay.

Community
  • 1
  • 1
user1898829
  • 3,437
  • 6
  • 34
  • 62

1 Answers1

6

I found that doing it this way is a lot better than relying on the touch event methods.

For your question about:

if (overlay is MKPolygon)

We need to cast the overlay to a polygon so we can get access to the points array, and rebuild the path. Although in my method below, you'll only need access to the points array is the renderer.path is nil. Since I'm using the overlays that are currently on the map, I'm confident that the path is not nil.

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    let tap = UITapGestureRecognizer(target: self, action: #selector(mapTapped(_:)))

    self.map.addGestureRecognizer(tap)
}

func mapTapped(_ gesture: UITapGestureRecognizer){

    let point = gesture.location(in: self.map)

    let coordinate = self.map.convert(point, toCoordinateFrom: nil)

    let mappoint = MKMapPointForCoordinate(coordinate)

    for overlay in self.map.overlays {

        if let polygon = overlay as? MKPolygon {

            guard let renderer = self.map.renderer(for: polygon) as? MKPolygonRenderer else { continue }

            let tapPoint = renderer.point(for: mappoint)

            if renderer.path.contains(tapPoint) {

                print("Tap was inside this polygon")

                break // If you have overlapping overlays then you'll need an array of overlays which the touch is in, so remove this line.
            }

            continue
        }

        if let circle = overlay as? MKCircle {

            let centerMP = MKMapPointForCoordinate(circle.coordinate)

            let distance = MKMetersBetweenMapPoints(mappoint, centerMP) // distance between the touch point and the center of the circle

            if distance <= circle.radius {

                print("Tap was inside this circle")

                break // If you have overlapping overlays then you'll need an array of overlays which the touch is in, so remove this line.
            }

            continue
        }
    }
}
Tristan Beaton
  • 1,742
  • 2
  • 14
  • 25
  • Both if statements are always false no matter where i tap. Overlay is type MKRoutePolyline and therefore the if let polygon = overlay as? MKPolygon is never true – user1898829 Jun 06 '17 at 08:55
  • Try just changing every occurrence of `MKPolygon` with `MKPolyline`. – Tristan Beaton Jun 06 '17 at 09:00
  • I think you need to change the `toCoordinateFrom: nil` to your mkmapview (for me its `toCoordinateFrom: self.mapView`, otherwise it can't convert the tap position correctly. – Milly Jan 10 '18 at 09:19
  • 1
    I understand what you are saying @Milly, for the purpose of this example the map view's bounds spanned the entire screen, so using the default coordinate scheme, by putting `nil` in that function, was fine. If your map view's origin isn't at `x:0, y:0` then you should pass the map view as the view. I should remind you that this is just an example of how to do this, and should be used as a guide, not rewritten word for word. It is the programmer's responsibility to understand the code he is writing and to implement it properly. – Tristan Beaton Jan 10 '18 at 20:12
  • Thanks for the info, now I understand properly! – Milly Jan 10 '18 at 23:11
  • I needed support for MKMultiPolygonRenderer as well as MKPolygonRenderer. They both inherit from MKOverlayPathRenderer which is all that is needed for the point(for:) and path.contains() portions of the code so it worked well for my use case. – SuperGuyAbe Mar 23 '22 at 16:24