Detección de toques en MKOverlay en iOS7 (MKOverlayRenderer)

Tengo un MKMapView con posiblemente cientos de polígonos dibujados. Utilizando MKPolygon y MKPolygonRenderer como se supone uno en iOS7.

Lo que necesito es una forma de actuar sobre el usuario que toca uno de los polígonos. Representan un área en el map con cierta densidad de población, por ejemplo. En iOS6, las MKOverlays se dibujaron como MKOverlayViews, por lo que la detección táctil fue más sencilla. Ahora que uso renderers realmente no veo cómo se supone que esto debe hacerse.

No estoy seguro de si esto sea útil o incluso relevante, pero como reference, publicaré un código:

Esto agrega todas las MKOverlays a MKMapView usando mapData.

-(void)drawPolygons{ self.polygonsInfo = [NSMutableDictionary dictionary]; NSArray *polygons = [self.mapData valueForKeyPath:@"polygons"]; for(NSDictionary *polygonInfo in polygons){ NSArray *polygonPoints = [polygonInfo objectForKey:@"boundary"]; int numberOfPoints = [polygonPoints count]; CLLocationCoordinate2D *coordinates = malloc(numberOfPoints * sizeof(CLLocationCoordinate2D)); for (int i = 0; i < numberOfPoints; i++){ NSDictionary *pointInfo = [polygonPoints objectAtIndex:i]; CLLocationCoordinate2D point; point.latitude = [[pointInfo objectForKey:@"lat"] floatValue]; point.longitude = [[pointInfo objectForKey:@"long"] floatValue]; coordinates[i] = point; } MKPolygon *polygon = [MKPolygon polygonWithCoordinates:coordinates count:numberOfPoints]; polygon.title = [polygonInfo objectForKey:@"name"]; free(coordinates); [self.mapView addOverlay:polygon]; [self.polygonsInfo setObject:polygonInfo forKey:polygon.title]; // Saving this element information, indexed by title, for later use on mapview delegate method } } 

Luego está el método delegado para devolver un MKOverlayRenderer para cada MKOverlay:

 -(MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id<MKOverlay>)overlay{ /* ... */ MKPolygon *polygon = (MKPolygon*) overlay; NSDictionary *polygonInfo = [self.polygonsInfo objectForKey:polygon.title]; // Retrieving element info by element title NSDictionary *colorInfo = [polygonInfo objectForKey:@"color"]; MKPolygonRenderer *polygonRenderer = [[MKPolygonRenderer alloc] initWithPolygon:polygon]; polygonRenderer.fillColor = [UIColor colorWithRed:[[colorInfo objectForKey:@"networking"] floatValue] green:[[colorInfo objectForKey:@"green"] floatValue] blue:[[colorInfo objectForKey:@"blue"] floatValue] alpha:[[polygonInfo objectForKey:@"opacity"] floatValue]]; return polygonRenderer; /* ... */ } 

Lo he hecho.

¡Gracias a incanus y Anna !

Básicamente agrego un TapGestureRecognizer a MapView, convierto el punto tocado en las coorderadas del map, reviso mis superposiciones y compruebo con CGPathContainsPoint.

Agregar TapGestureRecognizer. Hice ese truco de agregar un segundo gesto de doble toque, de modo que el gesto de un solo toque no se dispara al hacer doble toque para acercar el map. ¡Si alguien conoce una mejor manera, me alegra saberlo!

 UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleMapTap:)]; tap.cancelsTouchesInView = NO; tap.numberOfTapsRequinetworking = 1; UITapGestureRecognizer *tap2 = [[UITapGestureRecognizer alloc] init]; tap2.cancelsTouchesInView = NO; tap2.numberOfTapsRequinetworking = 2; [self.mapView addGestureRecognizer:tap2]; [self.mapView addGestureRecognizer:tap]; [tap requireGestureRecognizerToFail:tap2]; // Ignore single tap if the user actually double taps 

Entonces, en el manejador de tomas:

 -(void)handleMapTap:(UIGestureRecognizer*)tap{ CGPoint tapPoint = [tap locationInView:self.mapView]; CLLocationCoordinate2D tapCoord = [self.mapView convertPoint:tapPoint toCoordinateFromView:self.mapView]; MKMapPoint mapPoint = MKMapPointForCoordinate(tapCoord); CGPoint mapPointAsCGP = CGPointMake(mapPoint.x, mapPoint.y); for (id<MKOverlay> overlay in self.mapView.overlays) { if([overlay isKindOfClass:[MKPolygon class]]){ MKPolygon *polygon = (MKPolygon*) overlay; CGMutablePathRef mpr = CGPathCreateMutable(); MKMapPoint *polygonPoints = polygon.points; for (int p=0; p < polygon.pointCount; p++){ MKMapPoint mp = polygonPoints[p]; if (p == 0) CGPathMoveToPoint(mpr, NULL, mp.x, mp.y); else CGPathAddLineToPoint(mpr, NULL, mp.x, mp.y); } if(CGPathContainsPoint(mpr , NULL, mapPointAsCGP, FALSE)){ // ... found it! } CGPathRelease(mpr); } } } 

Podría pedir el MKPolygonRenderer que ya tiene la propiedad "ruta" y usarlo, pero por alguna razón siempre es nulo. Leí a alguien que decía que podía llamar a invalidatePath en el renderizador y que sí completaba la propiedad de la ruta pero que simplemente parecía incorrecto ya que el punto nunca se encuentra dentro de ninguno de los polígonos. Es por eso que reconstruyo el path desde los puntos. De esta manera, ni siquiera necesito el renderizador y solo hago uso del object MKPolygon.

Encontré una solución similar a @manecosta, pero usa API Apple existentes para detectar la intersección con más facilidad.

Cree un MKMapRect desde la location de toque en la Vista. Utilicé 0.000005 como lat / long delta para representar el toque de un usuario.

  CGPoint tapPoint = [tap locationInView:self.mapView]; CLLocationCoordinate2D tapCoordinate = [self.mapView convertPoint:tapPoint toCoordinateFromView:self.mapView]; MKCoordinateRegion tapCoordinateRection = MKCoordinateRegionMake(tapCoordinate, MKCoordinateSpanMake(0.000005, 0.000005)); MKMapRect touchMapRect = MKMapRectForCoordinateRegion(tapCoordinateRection); 

Busca a través de todas las superposiciones de MapView y usa la function 'intersectsMapRect:' para determinar si tu superposition actual se cruza con el MapRect que creaste arriba.

  for (id<MKOverlay> overlay in self.mapView.overlays) { if([overlay isKindOfClass:[MKPolyline class]]){ MKPolyline *polygon = (MKPolyline*) overlay; if([polygon intersectsMapRect:touchMapRect]){ NSLog(@"found polygon:%@",polygon); } } } 

Aquí está mi path en Swift

 @IBAction func revealRegionDetailsWithLongPressOnMap(sender: UILongPressGestureRecognizer) { if sender.state != UIGestureRecognizerState.Began { return } let touchLocation = sender.locationInView(protectedMapView) let locationCoordinate = protectedMapView.convertPoint(touchLocation, toCoordinateFromView: protectedMapView) //println("Taped at lat: \(locationCoordinate.latitude) long: \(locationCoordinate.longitude)") var point = MKMapPointForCoordinate(locationCoordinate) var mapRect = MKMapRectMake(point.x, point.y, 0, 0); for polygon in protectedMapView.overlays as! [MKPolygon] { if polygon.intersectsMapRect(mapRect) { println("found") } } } 

(Para Swift 3 ) No estoy seguro de por qué las personas agregan un UIGestureRecognizer a mapView cuando mapView ya tiene varios gestores de reconocimiento de gestos en ejecución. Descubrí que estos methods inhiben la funcionalidad normal de mapView, en particular, al tocar una anotación. En cambio, recomendaría subclasificar el mapView y anular el método touchesEnded. Luego podemos usar los methods sugeridos por otros en este hilo y usar un método de delegado para decirle al ViewController que haga lo que sea necesario. El parámetro "toques" tiene un set de objects UITouch que podemos usar:

 import UIKit import MapKit protocol MapViewTouchDelegate: class { func polygonsTapped(polygons: [MKPolygon]) } class MyMapViewSubclass: MapView { weak var mapViewTouchDelegate: MapViewTouchDelegate? 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 point = MKMapPointForCoordinate(locationCoordinate) let mapRect = MKMapRectMake(point.x, point.y, 0, 0); var polygons: [MKPolygon] = [] for polygon in self.overlays as! [MKPolygon] { if polygon.intersects(mapRect) { print("found") polygons.append(polygon) } } if polygons.count > 0 { //Do stuff here like use a delegate: self.mapViewTouchDelegate?.polygonsTapped(polygons: polygons) } } } super.touchesEnded(touches, with: event) } 

No olvide configurar ViewController como mapViewTouchDelegate. También encontré útil hacer una extensión para MKPolygon:

 import MapKit extension MKPolygon { func contains(coordinate: CLLocationCoordinate2D) -> Bool { let point = MKMapPointForCoordinate(coordinate) let mapRect = MKMapRectMake(point.x, point.y, 0, 0) return self.intersects(mapRect) } } 

Entonces, la function puede ser un poco más limpia y la extensión puede ser útil en otro lado. ¡Además es rápido!

  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) var polygons: [MKPolygon] = [] for polygon in self.overlays as! [MKPolygon] { if polygon.contains(coordinate: locationCoordinate) { print("found") polygons.append(polygon) } } if polygons.count > 0 { //Do stuff here like use a delegate: self.mapViewTouchDelegate?.polygonsTapped(polygons: polygons) } } } super.touchesEnded(touches, with: event) } 

No podrá determinar esto utilizando las API que proporciona Apple. Lo mejor que podría hacer con MapKit sería mantener una database separada de todas sus coorderadas de polígono , así como el order en que se astackn las versiones procesadas. Luego, cuando el usuario toca un punto, puede hacer una consulta espacial en sus datos secundarios para encontrar el (los) polígono (s) en cuestión combinado con el order de astackmiento para determinar a cuál tocaron.

Una forma más fácil de hacerlo si los polígonos son relativamente estáticos sería crear una superposition de maps en TileMill con sus propios datos de interactividad. Aquí hay un map de ejemplo que contiene datos de interactividad para países:

https://a.tiles.mapbox.com/v3/examples.map-zmy97flj/page.html

Observe cómo se recuperan algunos datos de nombre e image cuando se mueven en la versión web. Usando el MapBox iOS SDK , que es un clon de MapKit de código abierto, puede leer esos mismos datos en gestos arbitrarios. Una aplicación de ejemplo que muestra esto está aquí:

https://github.com/mapbox/mapbox-ios-example

Esa solución podría funcionar para su problema y es bastante liviano en comparación con una database secundaria y el cálculo justo a time del área tocada.

PARA SWIFT 2.1 Encuentre un punto / coorderada en un polígono

Aquí está la lógica, sin pulsar gestos, para encontrar una anotación dentro de un polígono.

  //create a polygon var areaPoints = [CLLocationCoordinate2DMake(50.911864, 8.062454),CLLocationCoordinate2DMake(50.912351, 8.068247),CLLocationCoordinate2DMake(50.908536, 8.068376),CLLocationCoordinate2DMake(50.910159, 8.061552)] func addDriveArea() { //add the polygon let polygon = MKPolygon(coordinates: &areaPoints, count: areaPoints.count) MapDrive.addOverlay(polygon) //starts the mapView-Function } func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer! { if overlay is MKPolygon { let renderer = MKPolygonRenderer(overlay: overlay) renderer.strokeColor = UIColor.blueColor() renderer.lineWidth = 2 let coordinate = CLLocationCoordinate2D(latitude: CLLocationDegrees(50.917627), longitude: CLLocationDegrees(8.069562)) let mappoint = MKMapPointForCoordinate(coordinate) let point = polygonView.pointForMapPoint(mappoint) let mapPointAsCGP = CGPointMake(point.x, point.y); let isInside = CGPathContainsPoint(renderer.path, nil, mapPointAsCGP, false) print("IsInside \(isInside)") //true = found return renderer } else { return nil } } 

Estoy considerando utilizar tanto la superposition como la anotación de pin. Recibo el toque del pin asociado a la superposition.