web-dev-qa-db-ja.com

iOS7でのMKOverlayのタッチの検出(MKOverlayRenderer)

おそらく数百のポリゴンが描画されたMKMapViewがあります。 iOS7では、MKPolygonとMKPolygonRendererを1つとして使用することが想定されています。

私が必要としているのは、ポリゴンの1つに触れているユーザーに作用する方法です。たとえば、特定の人口密度を持つマップ上の領域を表します。 iOS6では、MKOverlaysはMKOverlayViewsとして描画されたため、タッチ検出がより簡単になりました。現在レンダラーを使用しているので、これがどのように行われるのか実際にはわかりません。

これが役立つか、関連性があるかどうかはわかりませんが、参考として、いくつかのコードを投稿します。

これにより、mapDataを使用してすべてのMKOverlaysが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;

    /* ... */
}
24
manecosta

私はそれをしました。

incanusAnnaに感謝します!

基本的に、TapGestureRecognizerをMapViewに追加し、タップしたポイントをマップ座標に変換し、オーバーレイを調べて、CGPathContainsPointで確認します。

TapGestureRecognizerを追加します。 2番目のダブルタップジェスチャを追加するというトリックを実行しました。これにより、ダブルタップしてマップをズームするときにシングルタップジェスチャが実行されなくなります。誰かがもっと良い方法を知っているなら、私は聞いてうれしいです!

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を要求して使用することもできますが、何らかの理由で常にnilです。レンダラーでinvalidatePathを呼び出すことができ、pathプロパティを埋めることができると言っている人を読んだことがありますが、どのポリゴン内にもポイントが見つからないため、間違っているようです。そのため、ポイントからパスを再構築します。この方法では、レンダラーも必要なく、MKPolygonオブジェクトを使用するだけです。

26
manecosta

[〜#〜] updated [〜#〜](For Swift 3&4)mapViewにすでにUIGestureRecognizerが追加されているのに、なぜ人々がUIGestureRecognizerをmapViewに追加しているのかわかりません実行中のジェスチャレコグナイザーの数。これらのメソッドは、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 contain(coor: CLLocationCoordinate2D) -> Bool {
        let polygonRenderer = MKPolygonRenderer(polygon: self)
        let currentMapPoint: MKMapPoint = MKMapPoint(coor)
        let polygonViewPoint: CGPoint = polygonRenderer.point(for: currentMapPoint)
        if polygonRenderer.path == nil {
          return false
        }else{
          return polygonRenderer.path.contains(polygonViewPoint)
        }
    }
}

そうすれば、関数は少しすっきりし、拡張機能はどこか別の場所で役立つかもしれません。さらに、それはより迅速です!

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

@manecostaに似たソリューションを見つけましたが、既存のApple APIを使用して、交差をより簡単に検出します。

ビューのタップ位置から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);

すべてのMapViewオーバーレイを検索し、「intersectsMapRect:」関数を使用して、現在のオーバーレイが上記で作成した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);
            }
        }
    }
5
Austin Marusco

これが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
DogCoffee

Appleが提供するAPIを使用してこれを判断することはできません。MapKitでできる最善のことは、すべてのポリゴン座標の個別のデータベースを維持することですおよびレンダリングされたバージョンがスタックされる順序次に、ユーザーがポイントに触れたときに、セカンダリデータに対して空間クエリを実行してポリゴンを見つけることができます( s)問題のスタック順序と組み合わせて、どちらに触れたかを判別します。

ポリゴンが比較的静的である場合にこれを行う簡単な方法は、 TileMill に独自のインタラクティブデータを使用してマップオーバーレイを作成することです。国の双方向性データを含むマップの例を次に示します。

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

Webバージョンでマウスオーバーすると、名前と画像のデータがどのように取得されるかに注目してください。オープンソースのMapKitクローンである MapBox iOS SDK を使用すると、任意のジェスチャで同じデータを読み取ることができます。これを示すサンプルアプリは次のとおりです。

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

その解決策はあなたの問題に役立つかもしれず、二次データベースと接触した領域のジャストインタイム計算と比較してかなり軽量です。

1
incanus

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
    }
}
0
kuzdu

オーバーレイとピン注釈の両方を使用することを検討しています。オーバーレイに関連付けられているピンからタッチを取得します。

0
yukhDev