determinar si MKMapView fue arrastrado / movido

84

¿Hay alguna forma de determinar si se arrastró un MKMapView?

Quiero obtener la ubicación del centro cada vez que un usuario arrastra el mapa usando, CLLocationCoordinate2D centre = [locationMap centerCoordinate];pero necesitaría un método delegado o algo que se active tan pronto como el usuario navegue con el mapa.

Gracias por adelantado

hgbnerd
fuente

Respuestas:

22

Mire la referencia MKMapViewDelegate .

Específicamente, estos métodos pueden ser útiles:

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

Asegúrese de que la propiedad delegada de su vista de mapa esté configurada para que se llamen a esos métodos.


fuente
1
Muchas gracias. - (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animatedhizo el trabajo.
hgbnerd
2
Gran solucion Perfecto para recargar anotaciones en el mapa cuando el usuario cambia de ubicación
Alejandro Luengo
176
-1 porque esta solución no le dice si el usuario arrastró el mapa. El regionWillChangeAnimated ocurre si el usuario gira el dispositivo u otro método acerca el mapa, no necesariamente en respuesta al arrastre.
CommaToast
Gracias @CommaToast Encontré el mismo problema con esta 'respuesta'
cleverbit
3
La solución de @mobi detecta los gestos de los usuarios (sí, todos) comprobando los reconocedores de gestos internos de MapView. ¡Agradable!
Felix Alcala
236

El código de la respuesta aceptada se activa cuando se cambia la región por cualquier motivo. Para detectar correctamente un arrastre de mapa, debe agregar un UIPanGestureRecognizer. Por cierto, este es el reconocedor de gestos de arrastre (panorámica = arrastrar).

Paso 1: agregue el reconocedor de gestos en viewDidLoad:

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

Paso 2: agregue el protocolo UIGestureRecognizerDelegate al controlador de vista para que funcione como delegado.

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

Paso 3: Y agregue el siguiente código para que UIPanGestureRecognizer funcione con los reconocedores de gestos ya existentes en MKMapView:

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

Paso 4: en caso de que desee llamar a su método una vez en lugar de 50 veces por arrastre, detecte ese estado de "arrastre finalizado" en su selector:

- (void)didDragMap:(UIGestureRecognizer*)gestureRecognizer {
    if (gestureRecognizer.state == UIGestureRecognizerStateEnded){
        NSLog(@"drag ended");
    }
}
Jano
fuente
Sé que esta es una publicación bastante antigua, pero me encanta tu idea anterior, tuve problemas para organizar mi aplicación con el método regionDidChange por sí solo con mi implementación y cuando vi esto, todo hizo clic y tienes tanta razón que regionDidChange se dispara por cualquier motivo que no es ideal con esto, puedo obtener un mapa para hacer exactamente lo que quiero, ¡así que felicitaciones por esto!
Alex McPherson
3
Si desea pizcas de captura demasiado, tendrá que añadir una UIPinchGestureRecognizerasí
Gregory Cosmo Haun
32
Tenga en cuenta que el desplazamiento de la vista del mapa tiene impulso, y el ejemplo anterior se activará tan pronto como finalice el gesto, pero antes de que la vista del mapa deje de moverse. Puede que haya una forma mejor, pero lo que he hecho es colocar una bandera cuando el gesto se detiene readyForUpdate, y luego registrar esa bandera - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated.
tv el
14
Tenga en cuenta que el usuario puede tocar dos veces con uno o dos dedos para hacer zoom, lo que cambiará la región pero no llamará a este reconocedor de panorámica.
SomeGuy
2
¿Por qué esta solución está en la parte inferior? ¡Es el mejor! Sí , la solución de @mobi es más simple pero esta es más segura.
Leslie Godwin
77

Esta es la única forma que funcionó para mí que detecta los cambios de panorámica y zoom iniciados por el usuario:

- (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
    }
}
Monigote de nieve
fuente
2
Esto funciona para mí, pero debe tenerse en cuenta que esto depende de la implementación interna de MKMapViewiOS. Esa implementación podría cambiar en cualquier actualización de iOS ya que no forma parte de la API.
progrmr
Esto funciona y me gusta más que la respuesta principal porque no altera lo que está allí.
QED
Gracias por la elegante solución de código frente a la manipulación de mapas de usuario.
djneely
32

(Solo la) versión Swift de la excelente solución de @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
    }
}
Choque de cabeza
fuente
2
Se ve bien, pero tuve que cambiarme self.mapView.subviews[0]aself.mapView.subviews[0] as! UIView
kmurph79
3
Esta solución (y la de Moby) no es tan excelente. No hay garantía de que Apple conservará la primera subvista de mapViews. Quizás en alguna versión futura, el primer subView de mapView no será un UIView. Entonces su código no es repelente de fallas. Intente agregar sus propios GestureRecognizers a MapView.
Samet DEDE
1
nota, para que esto funcione tuve que agregar self.map.delegate = selfa viewDidLoad
tylerSF
17

Solución Swift 3 a la respuesta de Jano anterior:

Agregue el protocolo UIGestureRecognizerDelegate a su ViewController

class MyViewController: UIViewController, UIGestureRecognizerDelegate

Cree el UIPanGestureRecognizer viewDidLoady configúrelo como delegateuno mismo

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

Agregue un método de protocolo para que su reconocedor de gestos funcione con los gestos MKMapView existentes

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

Agregue el método al que llamará el selector en su gesto de panorámica

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

        // do something here

    }
}
dst3p
fuente
¡Ésta es la solución! ¡Gracias!
Hilalkah
8

En mi experiencia, similar a "buscar mientras escribe", encontré que un temporizador es la solución más confiable. Elimina la necesidad de agregar reconocedores de gestos adicionales para desplazarse, pellizcar, rotar, tocar, tocar dos veces, etc.

La solucion es simple:

  1. Cuando cambie la región del mapa, configure / reinicie el temporizador
  2. Cuando se active el temporizador, cargue marcadores para la nueva región

    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
        }
    
    }
    
Eneko Alonso
fuente
7

Otra posible solución es implementar touchesMoved: (o touchesEnded :, etc.) en el controlador de vista que contiene su vista de mapa, así:

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

En algunos casos, esto podría ser más simple que usar reconocedores de gestos.

Aaron
fuente
6

También puede agregar un reconocedor de gestos a su mapa en Interface Builder. Conéctelo a una salida para su acción en su viewController, llamé al mío "mapDrag" ...

Entonces harás algo como esto en el .m de tu viewController:

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

Asegúrate de tener esto allí también:

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

Por supuesto, tendrá que convertir su viewController en un UIGestureRecognizerDelegate en su archivo .h para que eso funcione.

De lo contrario, el respondedor del mapa es el único que escucha el evento del gesto.

CommaToast
fuente
perfecto para la solución del guión gráfico. Buen trabajo conUIGestureRecognizerStateBegan
Jakub
5

Para reconocer cuándo terminó un gesto en la vista de mapa:

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

Esto es muy útil solo para realizar una consulta de base de datos después de que el usuario haya terminado de hacer zoom / rotar / arrastrar el mapa.

Para mí, el método regionDidChangeAnimated solo se llamó después de que se realizó el gesto, y no se llamó muchas veces mientras se arrastraba / hacía zoom / rotaba, pero es útil saber si se debió a un gesto o no.

Doug Voss
fuente
Este método no funcionó para mí. Tan pronto como la región mapView cambia del código, se activa que era del usuario ...
Maksim Kniazev
5

Muchas de estas soluciones están en el lado hacky / no es lo que Swift pretendía, así que opté por una solución más limpia.

Simplemente hago una subclase de MKMapView y anulo touchchesMoved. Si bien este fragmento no lo incluye, recomendaría crear un delegado o una notificación para transmitir la información que desee sobre el movimiento.

import MapKit

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

        print("Something moved")
    }
}

Deberá actualizar la clase en los archivos de su guión gráfico para apuntar a esta subclase, así como modificar los mapas que cree a través de otros medios.

Como se señaló en los comentarios, Apple desaconseja el uso de subclases MKMapView. Si bien esto queda a discreción del desarrollador, este uso en particular no modifica el comportamiento del mapa y me ha funcionado sin incidentes durante más de tres años. Sin embargo, el rendimiento pasado no indica compatibilidad futura, por lo tanto, advertencia emptor .

CodeBender
fuente
1
Esta parece la mejor solución. Lo he probado y parece que funciona bien. Creo que es bueno que otros sepan que Apple aconseja no subclasificar MKMapView: "Aunque no debe subclasificar la clase MKMapView en sí, puede obtener información sobre el comportamiento de la vista del mapa proporcionando un objeto delegado". Enlace: developer.apple.com/documentation/mapkit/mkmapview . Sin embargo, no tengo una opinión sólida sobre ignorar sus consejos de no subclase MKMapView y estoy abierto a aprender más de otros al respecto.
Andrej
1
"Esta parece la mejor solución a pesar de que Apple dice que no lo hagas" parece que tal vez no sea la mejor solución.
dst3p
3

La respuesta de Jano funcionó para mí, así que pensé en dejar una versión actualizada para Swift 4 / XCode 9 ya que no soy particularmente competente en Objective C y estoy seguro de que hay algunos otros que tampoco lo son.

Paso 1: agregue este código en viewDidLoad:

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

Paso 2: asegúrese de que su clase se ajuste a UIGestureRecognizerDelegate:

class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UIGestureRecognizerDelegate {

Paso 3: agregue la siguiente función para asegurarse de que su panGesture funcione simultáneamente con otros gestos:

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

Paso 4: Asegúrate de que tu método no se llame "50 veces por arrastre" como señala Jano correctamente:

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

* Tenga en cuenta la adición de @objc en el último paso. XCode forzará este prefijo en su función para que se compile.

Pigpocket
fuente
3

Puede verificar la propiedad animada si es falsa y luego el usuario arrastró el mapa

 func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    if animated == false {
        //user dragged map
    }
}
Roma
fuente
El usuario podría haber ampliado el mapa.
Victor Engel
2

Sé que esta es una publicación antigua, pero aquí mi código Swift 4/5 de la respuesta de Jano con los gestos Pan y 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
        }
    }
}

¡Disfrutar!

BossOz
fuente
Como se dijo anteriormente, esto no reconoce los zooms, pero debería ser Good Enough ™ para la mayoría.
Skoua
1
Funciona incluso para zooms. Lo acabo de probar;)
Baran Emre
1

Estaba tratando de tener una anotación en el centro del mapa que siempre esté en el centro del mapa sin importar los usos. Probé varios de los enfoques mencionados anteriormente, y ninguno de ellos fue lo suficientemente bueno. Finalmente encontré una forma muy simple de resolver esto, tomando prestada la respuesta de Anna y combinándola con la respuesta de Eneko. Básicamente trata el regionWillChangeAnimated como el inicio de un arrastre, y regionDidChangeAnimated como el final de uno, y usa un temporizador para actualizar el pin en tiempo real:

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()
}
Gadzair
fuente
0

Ingrese el código aquí Logré implementar esto de la manera más fácil, que maneja toda la interacción con el mapa (tocar / doble / N tocar con 1/2 / N dedos, pan con 1/2 / N dedos, pellizcar y rotaciones

  1. Crear gesture recognizery agregar al contenedor de la vista del mapa
  2. Establecer gesture recognizer's delegateen algún objeto implementandoUIGestureRecognizerDelegate
  3. Implementar gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch)método
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
}
Nikita Ivaniushchenko
fuente
-1

Primero , asegúrese de que su controlador de vista actual sea un delegado del mapa. Así que configure su delegado de Vista de mapa para sí mismo y agréguelo MKMapViewDelegatea su controlador de vista. Ejemplo a continuación.

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

Y agregue esto a su vista de mapa

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

En segundo lugar , agregue esta función que se activa cuando se mueve el mapa. Filtrará cualquier animación y solo se disparará si se interactúa con él.

func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
   if !animated {
       // User must have dragged this, filters out all animations
       // PUT YOUR CODE HERE
   }
}
Liam Bolling
fuente