在iOS7中检测MKOverlay的触摸(MKOverlayRenderer)

我有一个可能绘制数百个多边形的MKMapView。 假设在iOS7上使用MKPolygon和MKPolygonRenderer。

我需要的是用户触摸其中一个多边形的方式。 例如,它们代表地图上某个人口密度的地区。 在iOS6上,MKOverlay被绘制为MKOverlayViews,因此触摸检测更直接。 现在使用渲染器,我真的不知道这是怎么做的。

我不确定这会帮助甚至是相关的,但作为参考,我会张贴一些代码:

这使用mapData将所有MKOverlay添加到MKMapView。

-(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 } } 

然后是为每个MKOverlay返回一个MKOverlayRenderer的委托方法:

 -(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:@"red"] floatValue] green:[[colorInfo objectForKey:@"green"] floatValue] blue:[[colorInfo objectForKey:@"blue"] floatValue] alpha:[[polygonInfo objectForKey:@"opacity"] floatValue]]; return polygonRenderer; /* ... */ } 

我已经做到了

感谢incanus安娜

基本上,我添加一个TapGestureRecognizer到MapView,将点转换为地图坐标,通过我的覆盖,并检查与CGPathContainsPoint。

添加TapGestureRecognizer。 我做了添加第二个双击手势的技巧,以便在双击地图时双击手势不会被触发。 如果有人知道更好的方法,我很高兴听到!

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

然后,在水龙头处理器上:

 -(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); } } } 

我可以请求已经具有“path”属性并使用它的MKPolygonRenderer,但由于某种原因,它始终为零。 我读过一个人说,我可以在渲染器上调用invalidatePath,它填充path属性,但它似乎是错误的,因为在任何多边形内都找不到点。 这就是为什么我重buildpath的重点。 这样我甚至不需要渲染器,只是使用MKPolygon对象。

我find了一个类似于@manecosta的解决scheme,但它使用现有的Apple API来更轻松地检测交集。

从视图中的水龙头位置创build一个MKMapRect。 我用0.000005作为经纬度来代表用户的触摸。

  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); 

search所有MapView叠加层,并使用“intersectsMapRect:”函数确定当前叠加层是否与上面创build的MapRect相交。

  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); } } } 

这是我在斯威夫特的方式

 @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") } } } 

(对于Swift 3 )我不知道为什么人们添加一个UIGestureRecognizer到mapView时,mapView已经有一些手势识别器正在运行。 我发现这些方法禁止了mapView的正常function,特别是在注释上敲击。 相反,我build议inheritancemapView并覆盖touchesEnded方法。 然后,我们可以使用其他人在此线程中build议的方法,并使用委托方法告诉ViewController执行所需的任何操作。 “touches”参数有一组我们可以使用的UITouch对象:

 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) } 

不要忘记将ViewController设置为mapViewTouchDelegate。 我也发现它适合做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) } } 

那么这个函数可以稍微干净些,而且扩展可能对别的地方有帮助。 再加上它更快捷!

  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) } 

您无法使用Apple提供的API来确定这一点。 用MapKit可以做的最好的做法是维护一个单独的数据库,包括所有多边形坐标以及渲染版本的堆叠顺序。 然后,当用户触摸某个点时,可以对辅助数据执行空间查询,以查找与叠加顺序相关的多边形,以确定它们所触摸的哪一个。

如果多边形相对静态, 则更简单的方法是使用自己的交互数据在TileMill中创build地图叠加。 下面是一个包含国家交互性数据的示例地图:

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

请注意,在Web版本中如何隐藏某些名称和图像数据。 使用MapBox iOS SDK (开源MapKit克隆),您可以在任意手势上读取相同的数据。 一个示例应用程序显示在这里:

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

该解决scheme可能适用于您的问题,与辅助数据库相比非常轻便,并且即时计算所触及的区域。

我正在考虑使用叠加和引脚注释。 我从与叠加层关联的引脚处获得触摸。

FOR SWIFT 2.1在多边形中查找一个点/坐标

这里是逻辑,没有水龙头手势,find一个多边形内的注释。

  //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 } }