визначити, чи було перетягнуто / переміщено 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

2
Чудове рішення. Ідеально підходить для перезавантаження анотацій на карті, коли користувач змінює місце розташування
Алехандро Луенго,

176
-1, оскільки це рішення не повідомляє, чи користувач перетягував карту. RegionWillChangeAnimated відбувається, якщо користувач обертає пристрій або інший метод збільшує карту, не обов'язково у відповідь на перетягування.
CommaToast

Дякую @CommaToast Я виявив ту ж проблему з цією "відповіддю"
cleverbit

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 до контролера перегляду, щоб він працював як делегат.

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

Крок 3: І додайте такий код для UIPanGestureRecognizer для роботи з уже існуючими розпізнавачами жестів у MKMapView:

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

Крок 4: Якщо ви хочете викликати свій метод один раз, а не 50 разів за перетягування, виявіть, що стан "перетягування закінчився" у вашому селекторі:

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

Я знаю, що це досить стара публікація, але мені подобається ваша ідея вище, я намагався організувати свій додаток за допомогою методу regionDidChange самостійно з моєю реалізацією, і коли я побачив це, все це клацнуло, і ви настільки праві, що regionDidChange запускає з будь-якої причини, яка це не ідеально з цим, я можу отримати карту map, щоб робити саме те, що я хочу, тому слава за це!
Alex McPherson

3
Якщо ви хочете , щоб зловити щіпки теж, ви хочете додати , UIPinchGestureRecognizerа також
Gregory Cosmo Хон

32
Зверніть увагу, що прокрутка подання карти має імпульс, і наведений вище приклад буде вимкнено, як тільки жест закінчиться, але до того, як вигляд карти перестане рухатися. Можливо, є кращий спосіб, але те, що я зробив, - це встановити прапор, коли жест зупиняється readyForUpdate, а потім перевірити цей прапор - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated.
tvon

14
Зверніть увагу, що користувач може двічі натиснути один або два пальці, щоб збільшити масштаб, що змінить регіон, але не буде називати цей апарат розпізнавання.
SomeGuy

2
Чому це рішення внизу? Це найкращий! Так , рішення @mobi простіше, але це безпечніше.
Леслі Годвін

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. Ця реалізація може змінитися в будь-якому оновленні iOS, оскільки воно не є частиною API.
progrmr

Це працює, і мені це подобається більше, ніж провідна відповідь, оскільки це не змінює того, що є.
QED,

Дякуємо за елегантне рішення коду проти маніпуляцій з картою користувача.
djneely

32

(Тільки) чудового рішення @ mobi :

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

3
Це (і Moby's) рішення не таке чудове. Немає гарантії, що Apple збереже перший підпрогляд mapViews. Можливо, у якійсь майбутній версії перший підгляд ViewView mapView не буде UIView. Отже, ваш код не захищає від збоїв. Спробуйте додати свої власні GestureRecognizers до MapView.
Samet DEDE

1
Примітка, щоб отримати цю роботу, мені довелося додати self.map.delegate = selfviewDidLoad
tylerSF

17

Рішення Swift 3 на відповідь Яно вище:

Додайте протокол UIGestureRecognizerDelegate до свого ViewController

class MyViewController: UIViewController, UIGestureRecognizerDelegate

Створіть UIPanGestureRecognizer 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

Ви також можете додати розпізнавач жестів на свою карту в Interface Builder. Пов’яжіть це з торговою точкою за його дії у вашому viewController, я назвав шахту "mapDrag" ...

Тоді ви зробите щось подібне у своєму viewController .m:

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

Переконайтеся, що у вас там теж є:

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

Звичайно, вам доведеться зробити ваш viewController UIGestureRecognizerDelegate у вашому файлі .h, щоб це працювало.

В іншому випадку відповідач карти є єдиним, хто чує подію жесту.


ідеально підходить для розкадрування. Хороша робота зUIGestureRecognizerStateBegan
Якубом

5

Щоб розпізнати, коли який-небудь жест закінчився на перегляді карти:

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

Це дуже корисно лише для виконання запиту до бази даних після того, як користувач закінчить масштабування / обертання / перетягування карти навколо.

Для мене метод regionDidChangeAnimated викликався лише після того, як був виконаний жест, і його не викликали багато разів під час перетягування / масштабування / обертання, але корисно знати, чи це було обумовлено жестом чи ні.



Цей метод у мене не спрацював. Як тільки регіон mapView змінюється з коду, спрацьовує, що це був користувач ...
Максим Князєв

5

Багато з цих рішень на хакі / не те, що задумав Свіфт, тому я обрав більш чисте рішення.

Я просто підклас MKMapView і перевизначення дотиків Move. Хоча цей фрагмент його не включає, я б рекомендував створити делегата або сповіщення, щоб передати будь-яку інформацію, яку ви хочете про рух.

import MapKit

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

        print("Something moved")
    }
}

Вам потрібно буде оновити клас у файлах розкадровки, щоб вказати на цей підклас, а також змінити будь-які карти, створені вами іншими способами.

Як зазначається в коментарях, Apple не рекомендує використовувати підкласифікацію MKMapView. Хоча це випадає на розсуд розробника, це конкретне використання не змінює поведінку карти і працює для мене без інцидентів вже більше трьох років. Однак попередні результати не вказують на сумісність у майбутньому, тому застережте emptor .


1
Це здається найкращим рішенням. Я протестував це, і, здається, працює нормально. Думаю, добре, щоб інші усвідомлювали, що Apple радить не підкласувати MKMapView: "Хоча вам не слід підкласувати сам клас MKMapView, ви можете отримати інформацію про поведінку карти, надавши об'єкт-делегат." Посилання: developer.apple.com/documentation/mapkit/mkmapview . Однак у мене немає твердої думки щодо ігнорування їх поради не відкладати підклас MKMapView, і я відкритий, щоб дізнатись більше про це щодо інших.
Андрій

1
"Це здається найкращим рішенням, хоча Apple каже, що не робіть цього", здається, можливо, це не найкраще рішення.
dst3p

3

Відповідь Яно спрацювала для мене, тому я подумав залишити оновлену версію для Swift 4 / XCode 9, оскільки я не особливо володію ціллю C, і я впевнений, що є кілька інших, які теж не є.

Крок 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: І забезпечення того, щоб ваш метод не називався "50 разів на перетягування", як слушно зазначає Яно:

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

* Зверніть увагу на додавання @objc на останньому кроці. XCode примусить цей префікс до вашої функції для її компіляції.


3

Ви можете перевірити наявність анімованих властивостей, якщо значення хибне, ніж перетягувана користувачем карта

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

Користувач міг збільшити карту.
Віктор Енгель

2

Я знаю, що це стара публікація, але тут мій Swift 4/5 код відповіді Яно з жестами Pan і Pinch.

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

Насолоджуйтесь!


Як вже було сказано раніше, це не розпізнає масштабування, але для більшості це повинно бути достатньо.
Skoua

1
Це працює навіть для масштабування. Я щойно тестував;)
Баран Емре

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

введіть код тут Мені вдалося реалізувати це найпростішим способом, який обробляє всю взаємодію з картою (натискання / подвійне / N натискання 1/2 / 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

По-перше , переконайтеся, що ваш поточний контролер перегляду є делегатом карти. Отже, встановіть для делегата «Перегляд карти» значення «самостійно» та додайте 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
   }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.