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

时间:2013-12-31 13:25:51

标签: ios touch mkmapview mapkit mkoverlay

我有一个可能有数百个多边形的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;

    /* ... */
}

8 个答案:

答案 0 :(得分:26)

我已经完成了。

感谢 incanus Anna

基本上我将一个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属性但它似乎错了,因为在任何多边形中都找不到该点。这就是为什么我从点重建路径。这样我甚至不需要渲染器,只需使用MKPolygon对象。

答案 1 :(得分:10)

更新(对于 Swift 3&amp; 4 )我不确定当mapView已经运行了多个手势识别器时,为什么人们会向mapView添加UIGestureRecognizer 。我发现这些方法会抑制mapView的正常功能,特别是点击注释。相反,我建议继承mapView并覆盖touchesEnded方法。然后我们可以使用其他人在这个线程中建议的方法,并使用委托方法告诉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)
               var polygons: [MKPolygon] = []
               for polygon in self.overlays as! [MKPolygon] {
                   let renderer = MKPolygonRenderer(polygon: polygon)
                   let mapPoint = MKMapPointForCoordinate(locationCoordinate)
                   let viewPoint = renderer.point(for: mapPoint)
                   if renderer.path.contains(viewPoint) {
                       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 renderer = MKPolygonRenderer(polygon: self)
       let mapPoint = MKMapPointForCoordinate(coordinate)
       let viewPoint = renderer.point(for: mapPoint)
       return renderer.path.contains(viewPoint)
   }
}

然后该功能可以更清洁,扩展可能在其他地方有用。再加上它太棒了!

    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) {
                    polygons.append(polygon)
                }
            }
            if polygons.count > 0 {
            //Do stuff here like use a delegate:
                self.mapViewTouchDelegate?.polygonsTapped(polygons: polygons)
            }
        }
    }

    super.touchesEnded(touches, with: event)
}

答案 2 :(得分:5)

我找到了类似于@manecosta的解决方案,但它使用现有的Apple API更轻松地检测交叉点。

从视图中的点按位置创建MKMapRect。我使用0.000005作为lat / long delta来表示用户的触摸。

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

搜索所有MapView叠加层并使用&#39; intersectsMapRect:&#39;用于确定当前叠加是否与您在上面创建的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);
            }
        }
    }

答案 3 :(得分:4)

这是我在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")
        }
    }
}

答案 4 :(得分:1)

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

如果多边形相对静态,更简单的方法是在TileMill中创建具有自己的交互数据的地图叠加层。以下是包含各国互动数据的示例地图:

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

注意一些名字&amp;在Web版本中鼠标悬停时检索图像数据。使用MapBox iOS SDK(一个开源的MapKit克隆),您可以在任意手势上读取相同的数据。显示此信息的示例应用程序位于:

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

该解决方案可能适用于您的问题,并且与辅助数据库和触摸区域的即时计算相比非常轻量级。

答案 5 :(得分:0)

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

答案 6 :(得分:0)

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

这是没有敲击手势的逻辑,用于在多边形内找到注释。

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

答案 7 :(得分:0)

基于@davidrynn answer,我获得了更加动态和更新的结果。

快捷键5

子类MKMapView:

two

}

之后,我为每种mkOverlay类型创建了多个扩展名:

MKKCircle

public class MapView: MKMapView {

public var mapViewProtocol: MapViewProtocol?

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

    if let touch = touches.first {

        if touch.tapCount == 1 {

            let touchLocation: CGPoint = touch.location(in: self)
            let locationCoordinate: CLLocationCoordinate2D = self.convert(touchLocation, toCoordinateFrom: self)

            var mkCircleList: [MKCircle] = self.overlays.compactMap { $0 as? MKCircle }
            mkCircleList = mkCircleList.filter { $0.contains(locationCoordinate) }
            if !mkCircleList.isEmpty {

                self.mapViewProtocol?.didTapMKCircles(self, mkCircleList)
            }

            var mkMultiPolygonList: [MKMultiPolygon] = self.overlays.compactMap { $0 as? MKMultiPolygon }
            mkMultiPolygonList = mkMultiPolygonList.filter { $0.contains(locationCoordinate) }
            if !mkMultiPolygonList.isEmpty {

                self.mapViewProtocol?.didTapMKMultiPolygons(self, mkMultiPolygonList)
            }

            var mkPolygonList: [MKPolygon] = self.overlays.compactMap { $0 as? MKPolygon }
            mkPolygonList = mkPolygonList.filter { $0.contains(locationCoordinate) }
            if !mkPolygonList.isEmpty {

                self.mapViewProtocol?.didTapMKPolygons(self, mkPolygonList)
            }

            var mkMultiPolylineList: [MKMultiPolyline] = self.overlays.compactMap { $0 as? MKMultiPolyline }
            mkMultiPolylineList = mkMultiPolylineList.filter { $0.contains(locationCoordinate) }
            if !mkMultiPolylineList.isEmpty {

                self.mapViewProtocol?.didTapMKMultiPolylines(self, mkMultiPolylineList)
            }

            var mkPolylineList: [MKPolyline] = self.overlays.compactMap { $0 as? MKPolyline }
            mkPolylineList = mkPolylineList.filter { $0.contains(locationCoordinate) }
            if !mkPolylineList.isEmpty {

                self.mapViewProtocol?.didTapMKPolylines(self, mkPolylineList)
            }

            //TODO
            //var mkTileOverlayList: [MKTileOverlay] = self.overlays.compactMap { $0 as? MKTileOverlay }
            //mkTileOverlayList = mkTileOverlayList.filter { $0.contains(locationCoordinate) }


            self.mapViewProtocol?.didTapMap(self, locationCoordinate)
        }
    }

    super.touchesEnded(touches, with: event)
}

MKMultiPolygon

import Foundation
import MapKit

extension MKCircle {

    func contains(_ coordinate2D: CLLocationCoordinate2D) -> Bool {

        let renderer = MKCircleRenderer(circle: self)
        let currentMapPoint: MKMapPoint = MKMapPoint(coordinate)
        let viewPoint: CGPoint = renderer.point(for: currentMapPoint)
        if renderer.path == nil {

            return false
        } else {

            return renderer.path.contains(viewPoint)
        }
    }
}

MKMultiPolyline

import Foundation
import MapKit

@available(iOS 13.0, *)
extension MKMultiPolygon {

    func contains(_ coordinate2D: CLLocationCoordinate2D) -> Bool {

        return self.polygons.filter { $0.contains(coordinate2D) }.isEmpty ? false : true
    }
}

MKPolygon

    import Foundation
import MapKit

@available(iOS 13.0, *)
extension MKMultiPolyline {

    func contains(_ coordinate2D: CLLocationCoordinate2D) -> Bool {

        return self.polylines.filter { $0.contains(coordinate2D) }.isEmpty ? false : true
    }
}

MKPolyline

import Foundation
import MapKit

extension MKPolygon {

    func contains(_ coordinate2D: CLLocationCoordinate2D) -> Bool {

        let renderer = MKPolygonRenderer(polygon: self)
        let currentMapPoint: MKMapPoint = MKMapPoint(coordinate2D)
        let viewPoint: CGPoint = renderer.point(for: currentMapPoint)
        if renderer.path == nil {

            return false
        } else {

            return renderer.path.contains(viewPoint)
        }
    }
}

最后创建并实现该协议:

import Foundation
import MapKit

extension MKPolyline {

    func contains(_ coordinate2D: CLLocationCoordinate2D) -> Bool {

        let renderer = MKPolylineRenderer(polyline: self)
        let currentMapPoint: MKMapPoint = MKMapPoint(coordinate2D)
        let viewPoint: CGPoint = renderer.point(for: currentMapPoint)
        if renderer.path == nil {

            return false
        } else {

            return renderer.path.contains(viewPoint)
        }
    }
}