определить, был ли MKMapView перетащен / перемещен

84

Есть ли способ определить, перетаскивали ли MKMapView?

Я хочу получать центральное местоположение каждый раз, когда пользователь перетаскивает карту с помощью, CLLocationCoordinate2D centre = [locationMap centerCoordinate];но мне нужен метод делегата или что-то, что срабатывает, как только пользователь перемещается по карте.

заранее спасибо

Hgbnerd
источник

Ответы:

21

Посмотрите ссылку MKMapViewDelegate .

В частности, эти методы могут быть полезны:

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

Убедитесь, что свойство делегата представления карты установлено для вызова этих методов.


источник
1
Большое спасибо. - (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animatedсделал свою работу.
hgbnerd 05
2
Отличное решение. Идеально подходит для перезагрузки аннотаций на карте, когда пользователь меняет местоположение
Алехандро Луенго
178
-1, потому что это решение не сообщает вам, перетащил ли пользователь карту. RegionWillChangeAnimated происходит, если пользователь поворачивает устройство или другой метод масштабирует карту, не обязательно в ответ на перетаскивание.
CommaToast
Спасибо @CommaToast, я обнаружил ту же проблему с этим «ответом»
cleverbit
3
Решение @mobi обнаруживает жесты пользователя (да, все из них), проверяя внутренние распознаватели жестов mapviews. Ницца!
Felix Alcala
238

Код в принятом ответе срабатывает при изменении региона по любой причине. Чтобы правильно определить перетаскивание карты, вам нужно добавить 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 срабатывает по любой причине, которая не идеален с этим, я могу получить карту, чтобы делать именно то, что я хочу, так что за это спасибо!
Alex McPherson
3
Если вы хотите , чтобы поймать щепотки тоже, вы хотите добавить , UIPinchGestureRecognizerа также
Gregory Cosmo Хон
32
Обратите внимание, что прокрутка представления карты имеет импульс, и приведенный выше пример будет запущен, как только жест закончится, но до того, как представление карты перестанет двигаться. Возможно, есть способ получше, но я установил флаг, когда жест останавливается readyForUpdate, а затем проверил этот флаг - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated.
tvon
14
Обратите внимание, что пользователь может дважды нажать одним или двумя пальцами для увеличения, что изменит область, но не вызовет этот распознаватель панорамирования.
SomeGuy 02
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
У меня это работает, но следует отметить, что это зависит от внутренней реализации MKMapViewв iOS. Эта реализация может измениться в любом обновлении iOS, поскольку она не является частью API.
progrmr
Это работает, и мне он нравится больше, чем основной ответ, потому что он не меняет того, что там есть.
QED
Спасибо за элегантное решение манипулирования кодом и картой пользователя.
djneely
32

(Просто) Swift-версия отличного решения @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
Это (и решение Моби) не так уж и хорошо. Нет никакой гарантии, что Apple сохранит первое подпредставление mapViews. Возможно, в какой-то будущей версии первый subView mapView не будет UIView. Так что ваш код не отталкивает от сбоев. Попробуйте добавить свои собственные GestureRecognizers в MapView.
Самет ДЕДЕ
1
обратите внимание, чтобы это self.map.delegate = selfсработало, мне пришлось добавить в viewDidLoad
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

    }
}
dst3p
источник
Это решение! Благодаря!
Хилалка
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

Вы также можете добавить распознаватель жестов к своей карте в Интерфейсном Разработчике. Свяжите его с выходом для его действия в вашем 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 файле, чтобы это работало.

В противном случае ответчик карты - единственный, кто слышит событие жеста.

CommaToast
источник
идеально подходит для раскадровки. Хорошая работа с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

Многие из этих решений являются хакерскими / не такими, как задумал Swift, поэтому я выбрал более чистое решение.

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

import MapKit

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

        print("Something moved")
    }
}

Вам нужно будет обновить класс в ваших файлах раскадровки, чтобы указать на этот подкласс, а также изменить любые карты, которые вы создаете с помощью других средств.

Как отмечено в комментариях, Apple не рекомендует использовать подклассы MKMapView. Хотя это остается на усмотрение разработчика, это конкретное использование не меняет поведения карты и работает у меня без происшествий более трех лет. Однако прошлые показатели не указывают на совместимость в будущем, поэтому будьте осторожны .

CodeBender
источник
1
Это кажется лучшим решением. Я тестировал его, и, похоже, он работает нормально. Я думаю, что другим полезно знать, что Apple советует не создавать подклассы MKMapView: «Хотя вы не должны создавать подклассы самого класса MKMapView, вы можете получить информацию о поведении вида карты, предоставив объект делегата». Ссылка: developer.apple.com/documentation/mapkit/mkmapview . Однако у меня нет твердого мнения о том, чтобы игнорировать их совет не создавать подклассы MKMapView, и я готов узнать больше от других по этому поводу.
Андрей
1
«Это кажется лучшим решением, даже несмотря на то, что Apple говорит, что этого не следует делать», возможно, это не лучшее решение.
dst3p
4

Вы можете проверить анимированное свойство, если false, тогда пользователь перетащил карту

 func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    if animated == false {
        //user dragged map
    }
}
Рома
источник
Пользователь мог увеличить карту.
Виктор Энгель
3

Ответ Яно сработал для меня, поэтому я подумал, что оставлю обновленную версию для Swift 4 / XCode 9, поскольку я не особенно разбираюсь в Objective 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 принудительно установит этот префикс в вашей функции, чтобы она скомпилировалась.

Pigpocket
источник
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
        }
    }
}

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

BossOz
источник
Как было сказано ранее, это не распознает зум, но для большинства это должно быть достаточно хорошо ™.
Skoua
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

Во-первых , убедитесь, что ваш текущий контроллер представления является делегатом карты. Итак, установите своего делегата Map View на себя и добавьте 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
   }
}
Лиам Боллинг
источник