¿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
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
hizo el trabajo.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"); } }
fuente
UIPinchGestureRecognizer
asíreadyForUpdate
, y luego registrar esa bandera- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
.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 } }
fuente
MKMapView
iOS. Esa implementación podría cambiar en cualquier actualización de iOS ya que no forma parte de la API.(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 } }
fuente
self.mapView.subviews[0]
aself.mapView.subviews[0] as! UIView
self.map.delegate = self
a viewDidLoadSolución Swift 3 a la respuesta de Jano anterior:
Agregue el protocolo UIGestureRecognizerDelegate a su ViewController
class MyViewController: UIViewController, UIGestureRecognizerDelegate
Cree el UIPanGestureRecognizer
viewDidLoad
y configúrelo comodelegate
uno mismoviewDidLoad() { // 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 } }
fuente
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:
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 } }
fuente
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.
fuente
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.
fuente
UIGestureRecognizerStateBegan
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.
fuente
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 .fuente
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.
fuente
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 } }
fuente
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!
fuente
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() }
fuente
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
gesture recognizer
y agregar al contenedor de la vista del mapagesture recognizer's
delegate
en algún objeto implementandoUIGestureRecognizerDelegate
gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch)
métodofuente
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
MKMapViewDelegate
a 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 } }
fuente