MKMapViewがドラッグ/移動されたかどうかを確認します


84

MKMapViewがドラッグされたかどうかを判断する方法はありますか?

ユーザーCLLocationCoordinate2D centre = [locationMap centerCoordinate];がを使用してマップをドラッグするたびに中心位置を取得したいのですが、デリゲートメソッドなど、ユーザーがマップをナビゲートするとすぐに起動するものが必要です。

前もって感謝します

回答:


22

MKMapViewDelegateリファレンスを見てください。

具体的には、次の方法が役立つ場合があります。

- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated

これらのメソッドが呼び出されるように、マップビューのデリゲートプロパティが設定されていることを確認してください。


1
どうもありがとう。- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated仕事をしました。
hgbnerd 2011

2
素晴らしいソリューション。ユーザーが場所を変更したときに地図上の注釈を再読み込みするのに
最適

176
-1このソリューションでは、ユーザーがマップをドラッグしたかどうかはわかりません。regionWillChangeAnimatedは、ユーザーがデバイスを回転させたり、別のメソッドがマップをズームしたりした場合に発生しますが、必ずしもドラッグに応答する必要はありません。
CommaToast 2014年

ありがとう@CommaToast私はこの「答え」で同じ問題を見つけました
cleverbit 2014年

3
@mobiのソリューションは、mapviewsの内部ジェスチャレコグナイザーをチェックすることにより、ユーザージェスチャ(はい、すべて)を検出します。いいね!
フェリックスアルカラ

236

受け入れられた回答のコードは、何らかの理由でリージョンが変更されたときに発生します。マップドラッグを適切に検出するには、UIPanGestureRecognizerを追加する必要があります。ところで、これはドラッグジェスチャレコグナイザーです(パン=ドラッグ)。

手順1: viewDidLoadにジェスチャレコグナイザーを追加します。

-(void) viewDidLoad {
    [super viewDidLoad];
    UIPanGestureRecognizer* panRec = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(didDragMap:)];
    [panRec setDelegate:self];
    [self.mapView addGestureRecognizer:panRec];
}

手順2:プロトコルUIGestureRecognizerDelegateをView Controllerに追加して、デリゲートとして機能するようにします。

@interface MapVC : UIViewController <UIGestureRecognizerDelegate, ...>

ステップ3: UIPanGestureRecognizerに次のコードを追加して、MKMapViewの既存のジェスチャレコグナイザーと連携させます。

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES;
}

ステップ4:ドラッグごとに50回ではなく、1回メソッドを呼び出す場合は、セレクターで「ドラッグが終了した」状態を検出します。

- (void)didDragMap:(UIGestureRecognizer*)gestureRecognizer {
    if (gestureRecognizer.state == UIGestureRecognizerStateEnded){
        NSLog(@"drag ended");
    }
}

私はこれがかなり古い投稿であることを知っていますが、上記のあなたのアイデアが大好きです、私は私の実装でそれ自体でregionDidChangeメソッドを使ってアプリを整理するのに苦労しました、そしてこれを見たときそれはすべてクリックされましたこれでは理想的ではありません。私が望むことを正確に実行するためにマップを取得できる可能性があるので、これを称賛します。
Alex McPherson 2013年

3
ピンチもキャッチしたい場合はUIPinchGestureRecognizer、同様に追加する必要があります
Gregory Cosmo Haun 2013年

32
マップビューのスクロールには勢いがあり、上記の例は、ジェスチャが終了するとすぐに、マップビューの移動が停止する前に起動されることに注意してください。より良い方法があるかもしれませんが、私がやったことは、ジェスチャーが停止したときにフラグを設定し、、readyForUpdateそしてそのフラグをでチェックすること- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animatedです。
tvon 2014年

14
ユーザーは1本または2本の指をダブルタップしてズームできます。これにより、領域が変更されますが、このパンレコグナイザーは呼び出されません。
SomeGuy 2014

2
なぜこのソリューションが一番下にあるのですか?最高です!はい、@ mobiのソリューションはより単純ですが、これはより安全です。
レスリーゴッドウィン2016年

77

これは、ユーザーによって開始されたパンとズームの変更を検出する、私にとって有効な唯一の方法です。

- (BOOL)mapViewRegionDidChangeFromUserInteraction
{
    UIView *view = self.mapView.subviews.firstObject;
    //  Look through gesture recognizers to determine whether this region change is from user interaction
    for(UIGestureRecognizer *recognizer in view.gestureRecognizers) {
        if(recognizer.state == UIGestureRecognizerStateBegan || recognizer.state == UIGestureRecognizerStateEnded) {
            return YES;
        }
    }

    return NO;
}

static BOOL mapChangedFromUserInteraction = NO;

- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
{
    mapChangedFromUserInteraction = [self mapViewRegionDidChangeFromUserInteraction];

    if (mapChangedFromUserInteraction) {
        // user changed map region
    }
}

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
    if (mapChangedFromUserInteraction) {
        // user changed map region
    }
}

2
これは私にとってはうまくいきますが、これはMKMapViewiOSの内部実装に依存していることに注意してください。この実装はAPIの一部ではないため、iOSのアップデートで変更される可能性があります。
progrmr 2015

これはうまくいきます、そしてそれがそこにあるものを変えないので、私はそれが主要な答えよりも好きです。
QED 2015

コードとユーザーマップ操作の洗練されたソリューションをありがとう。
djneely 2015

32

(ちょうど)@mobiの優れたソリューションのSwiftバージョン:

private var mapChangedFromUserInteraction = false

private func mapViewRegionDidChangeFromUserInteraction() -> Bool {
    let view = self.mapView.subviews[0]
    //  Look through gesture recognizers to determine whether this region change is from user interaction
    if let gestureRecognizers = view.gestureRecognizers {
        for recognizer in gestureRecognizers {
            if( recognizer.state == UIGestureRecognizerState.Began || recognizer.state == UIGestureRecognizerState.Ended ) {
                return true
            }
        }
    }
    return false
}

func mapView(mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
    mapChangedFromUserInteraction = mapViewRegionDidChangeFromUserInteraction()
    if (mapChangedFromUserInteraction) {
        // user changed map region
    }
}

func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    if (mapChangedFromUserInteraction) {
        // user changed map region
    }
}

2
見た目は良さそうですが、変更self.mapView.subviews[0]する必要がありましたself.mapView.subviews[0] as! UIView
kmurph79 2015年

3
この(そしてmobyの)ソリューションはそれほど優れていません。AppleがmapViewsの最初のサブビューを保持するという保証はありません。将来のバージョンでは、mapViewの最初のサブビューはUIViewではなくなる可能性があります。したがって、コードはクラッシュ忌避剤ではありません。独自のGestureRecognizersをMapViewに追加してみてください。
Samet DEDE 2015

1
これを機能させるにはself.map.delegate = self、viewDidLoadに追加する必要がありました
tylerSF 2016年

17

上記のJanoの答えに対するSwift3ソリューション:

プロトコルUIGestureRecognizerDelegateをViewControllerに追加します

class MyViewController: UIViewController, UIGestureRecognizerDelegate

でUIPanGestureRecognizerを作成し、selfにviewDidLoad設定delegateします

viewDidLoad() {
    // add pan gesture to detect when the map moves
    let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.didDragMap(_:)))

    // make your class the delegate of the pan gesture
    panGesture.delegate = self

    // add the gesture to the mapView
    mapView.addGestureRecognizer(panGesture)
}

プロトコルメソッドを追加して、ジェスチャレコグナイザが既存のMKMapViewジェスチャで動作するようにします

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

パンジェスチャでセレクターによって呼び出されるメソッドを追加します

func didDragMap(_ sender: UIGestureRecognizer) {
    if sender.state == .ended {

        // do something here

    }
}

これが解決策です!ありがとう!
ヒラルカ

8

私の経験では、「入力中の検索」と同様に、タイマーが最も信頼できるソリューションであることがわかりました。パン、ピンチ、回転、タッピング、ダブルタッピングなどのジェスチャレコグナイザーを追加する必要がなくなります。

解決策は簡単です:

  1. 地図の地域が変わったら、タイマーをセット/リセットしてください
  2. タイマーが作動したら、新しいリージョンのマーカーをロードします

    import MapKit
    
    class MyViewController: MKMapViewDelegate {
    
        @IBOutlet var mapView: MKMapView!
        var mapRegionTimer: NSTimer?
    
        // MARK: MapView delegate
    
        func mapView(mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
            setMapRegionTimer()
        }
    
        func setMapRegionTimer() {
            mapRegionTimer?.invalidate()
            // Configure delay as bet fits your application
            mapRegionTimer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "mapRegionTimerFired:", userInfo: nil, repeats: false)
        }
    
        func mapRegionTimerFired(sender: AnyObject) {
            // Load markers for current region:
            //   mapView.centerCoordinate or mapView.region
        }
    
    }
    

7

別の可能な解決策は、次のように、マップビューを保持するビューコントローラーにtouchesMoved :(またはtouchesEnded:など)を実装することです。

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    [super touchesMoved:touches withEvent:event];

    for (UITouch * touch in touches) {
        CGPoint loc = [touch locationInView:self.mapView];
        if ([self.mapView pointInside:loc withEvent:event]) {
            #do whatever you need to do
            break;
        }
    }
}

これは、場合によっては、ジェスチャレコグナイザを使用するよりも簡単な場合があります。


6

InterfaceBuilderでマップにジェスチャレコグナイザーを追加することもできます。それをviewControllerでのアクションのアウトレットにリンクします。私は「mapDrag」と呼びました...

次に、viewControllerの.mで次のようなことを行います。

- (IBAction)mapDrag:(UIPanGestureRecognizer *)sender {
    if(sender.state == UIGestureRecognizerStateBegan){
        NSLog(@"drag started");
    }
}

あなたもそこにこれを持っていることを確認してください:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
    return YES;
}

もちろん、それを機能させるには、.hファイルでviewControllerをUIGestureRecognizerDelegateにする必要があります。

それ以外の場合、ジェスチャイベントを聞くのはマップのレスポンダーだけです。


ストーリーボードソリューションに最適です。素晴らしい仕事UIGestureRecognizerStateBegan
Jakub 2014年

5

マップビューでジェスチャがいつ終了したかを認識するには:

[ https://web.archive.org/web/20150215221143/http://b2cloud.com.au/tutorial/mkmapview-determining-whether-region-change-is-from-user-interaction/

これは、ユーザーがマップのズーム/回転/ドラッグを完​​了した後にのみデータベースクエリを実行する場合に非常に便利です。

私の場合、regionDidChangeAnimatedメソッドはジェスチャが実行された後にのみ呼び出され、ドラッグ/ズーム/回転中に何度も呼び出されることはありませんでしたが、ジェスチャが原因であるかどうかを知ることは役に立ちます。



この方法は私にはうまくいきませんでした。mapView領域がコードから変更されるとすぐに、それがユーザーからのものであることがトリガーされます...
Maksim Kniazev

5

これらのソリューションの多くはハッキーであり、Swiftが意図したものではないため、よりクリーンなソリューションを選択しました。

MKMapViewをサブクラス化し、touchesMovedをオーバーライドするだけです。このスニペットには含まれていませんが、移動に関して必要な情報を渡すためのデリゲートまたは通知を作成することをお勧めします。

import MapKit

class MapView: MKMapView {
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesMoved(touches, with: event)

        print("Something moved")
    }
}

このサブクラスを指すようにストーリーボードファイルのクラスを更新する必要があります。また、他の方法で作成したマップを変更する必要があります。

コメントに記載されているように、Appleはサブクラス化の使用を推奨していませんMKMapView。これは開発者の裁量に委ねられますが、この特定の使用法はマップの動作を変更せず、3年以上問題なく機能してきました。ただし、過去のパフォーマンスは将来の互換性を示すものではないため、エンプターに注意してください


1
これが最善の解決策のようです。私はそれをテストしました、そしてうまくいくようです。AppleがMKMapViewをサブクラス化しないようにアドバイスしていることを知っておくのは良いことだと思います。「MKMapViewクラス自体をサブクラス化するべきではありませんが、デリゲートオブジェクトを提供することでマップビューの動作に関する情報を取得できます。」リンク:developer.apple.com/documentation/mapkit/mkmapview。しかし、私はMKMapViewをサブクラス化しないという彼らのアドバイスを無視することについて強い意見を持っておらず、これに関して他の人からもっと学ぶことを開いています。
アンドレイ

1
「Appleはそうしないと言っているが、これは最善の解決策のようだ」多分それは最善の解決策ではないようだ。
dst3p

3

Janoの答えは私にとってはうまくいったので、Objective Cに特に熟練していないので、Swift 4 / XCode 9の更新バージョンを残すと思いました。また、そうでないものもいくつかあると確信しています。

ステップ1:このコードをviewDidLoadに追加します:

let panGesture = UIPanGestureRecognizer(target: self, action: #selector(didDragMap(_:)))
panGesture.delegate = self

ステップ2:クラスがUIGestureRecognizerDelegateに準拠していることを確認します。

class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UIGestureRecognizerDelegate {

ステップ3:次の関数を追加して、panGestureが他のジェスチャと同時に機能することを確認します。

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
}

ステップ4:そして、Janoが正しく指摘しているように、メソッドが「ドラッグごとに50回」と呼ばれないようにします。

@objc func didDragMap(_ gestureRecognizer: UIPanGestureRecognizer) {
    if (gestureRecognizer.state == UIGestureRecognizerState.ended) {
        redoSearchButton.isHidden = false
        resetLocationButton.isHidden = false
    }
}

*最後のステップで@objcが追加されていることに注意してください。XCodeは、関数をコンパイルするために、このプレフィックスを関数に強制します。


3

falseの場合はアニメーションプロパティを確認してから、ユーザーがマップをドラッグします

 func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    if animated == false {
        //user dragged map
    }
}

ユーザーは地図をズームできたはずです。
ビクターエンゲル

2

私はこれが古い投稿であることを知っていますが、ここではJanoの答えSwift4 / 5コードがパンとピンチのジェスチャーをします。

class MapViewController: UIViewController, MapViewDelegate {

    override func viewDidLoad() {
        super.viewDidLoad()

        let panGesture = UIPanGestureRecognizer(target: self, action: #selector(self.didDragMap(_:)))
        let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(self.didPinchMap(_:)))
        panGesture.delegate = self
        pinchGesture.delegate = self
        mapView.addGestureRecognizer(panGesture)
        mapView.addGestureRecognizer(pinchGesture)
    }

}

extension MapViewController: UIGestureRecognizerDelegate {

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }

    @objc func didDragMap(_ sender: UIGestureRecognizer) {
        if sender.state == .ended {
            //code here
        }
    }

    @objc func didPinchMap(_ sender: UIGestureRecognizer) {
        if sender.state == .ended {
            //code here
        }
    }
}

楽しい!


前に述べたように、これはズームを認識しませんが、ほとんどの場合、GoodEnough™である必要があります。
Skoua

1
ズームでも動作します。私はちょうどそれをテストしました;)
Baran Emre

1

マップの中央に注釈を付けようとしていましたが、用途が何であれ、常にマップの中央にあります。私は上記のアプローチのいくつかを試しましたが、どれも十分ではありませんでした。私は最終的に、アンナの答えを借りて、エネコの答えと組み合わせるという、これを解決する非常に簡単な方法を見つけました。基本的に、regionWillChangeAnimatedをドラッグの開始として扱い、regionDidChangeAnimatedをドラッグの終了として扱い、タイマーを使用してピンをリアルタイムで更新します。

var mapRegionTimer: Timer?
public func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
    mapRegionTimer?.invalidate()
    mapRegionTimer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true, block: { (t) in
        self.myAnnotation.coordinate = CLLocationCoordinate2DMake(mapView.centerCoordinate.latitude, mapView.centerCoordinate.longitude);
        self.myAnnotation.title = "Current location"
        self.mapView.addAnnotation(self.myAnnotation)
    })
}
public func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    mapRegionTimer?.invalidate()
}

0

ここにコードを入力してくださいこれを最も簡単な方法で実装できました。これは、マップとのすべての対話(1/2 / N指でのタッピング/ダブル/ Nタッピング、1/2 / N指でのパン、ピンチ、回転)を処理します。

  1. 作成gesture recognizerしてマップビューのコンテナに追加します
  2. gesture recognizer's delegate実装するオブジェクトに設定するUIGestureRecognizerDelegate
  3. gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch)メソッドを実装する
private func setupGestureRecognizers()
{
    let gestureRecognizer = UITapGestureRecognizer(target: nil, action: nil)
    gestureRecognizer.delegate = self
    self.addGestureRecognizer(gestureRecognizer)
}   

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool
{
    self.delegate?.mapCollectionViewBackgroundTouched(self)
    return false
}

-1

まず、現在のViewControllerがマップのデリゲートであることを確認します。したがって、マップビューデリゲートをselfに設定し、ViewControllerに追加MKMapViewDelegateします。以下の例。

class Location_Popup_ViewController: UIViewController, MKMapViewDelegate {
   // Your view controller stuff
}

そしてこれをマップビューに追加します

var myMapView: MKMapView = MKMapView()
myMapView.delegate = self

次に、マップが移動したときに起動されるこの関数を追加します。アニメーションを除外し、操作された場合にのみ起動します。

func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
   if !animated {
       // User must have dragged this, filters out all animations
       // PUT YOUR CODE HERE
   }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.