determinare se MKMapView è stato trascinato / spostato


84

C'è un modo per determinare se un MKMapView è stato trascinato?

Voglio ottenere la posizione centrale ogni volta che un utente trascina la mappa utilizzando, CLLocationCoordinate2D centre = [locationMap centerCoordinate];ma avrei bisogno di un metodo delegato o qualcosa che si attiva non appena l'utente naviga con la mappa.

Grazie in anticipo

Risposte:


21

Guarda il riferimento MKMapViewDelegate .

Nello specifico, questi metodi possono essere utili:

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

Assicurati che la proprietà delegato della visualizzazione mappa sia impostata in modo che tali metodi vengano chiamati.


1
Molte grazie. - (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animatedha fatto il lavoro.
hgbnerd

2
Ottima soluzione. Perfetto per ricaricare le annotazioni sulla mappa quando l'utente cambia posizione
Alejandro Luengo

178
-1 perché questa soluzione non ti dice se l'utente ha trascinato la mappa. RegionWillChangeAnimated si verifica se l'utente ruota il dispositivo o un altro metodo ingrandisce la mappa, non necessariamente in risposta al trascinamento.
CommaToast

Grazie @CommaToast ho riscontrato lo stesso problema con questa 'risposta'
cleverbit

3
La soluzione di @mobi rileva i gesti dell'utente (sì, tutti) controllando i riconoscitori di gesti interni di mapviews. Bello!
Felix Alcala

238

Il codice nella risposta accettata si attiva quando la regione viene modificata per qualsiasi motivo. Per rilevare correttamente un trascinamento della mappa è necessario aggiungere un UIPanGestureRecognizer. A proposito, questo è il riconoscimento dei gesti di trascinamento (panoramica = trascinamento).

Passaggio 1: aggiungi il riconoscimento dei gesti in viewDidLoad:

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

Passaggio 2: aggiungere il protocollo UIGestureRecognizerDelegate al controller di visualizzazione in modo che funzioni come delegato.

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

Passaggio 3: aggiungere il codice seguente affinché UIPanGestureRecognizer funzioni con i riconoscitori di gesti già esistenti in MKMapView:

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

Passaggio 4: nel caso in cui desideri chiamare il tuo metodo una volta invece di 50 volte per trascinamento, rileva lo stato "trascinamento terminato" nel selettore:

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

So che questo è un post abbastanza vecchio ma adoro la tua idea sopra, ho faticato a organizzare la mia app con il metodo regionDidChange da solo con la mia implementazione e quando ho visto questo è stato tutto cliccato e hai ragione che regionDidChange si attiva per qualsiasi motivo non è l'ideale con questo posso ottenere la mappa per fare esattamente quello che voglio quindi complimenti per questo!
Alex McPherson

3
Se si vuole pizzichi di cattura troppo, ti consigliamo di aggiungere un UIPinchGestureRecognizercosì
Gregory Cosmo Haun

32
Tieni presente che lo scorrimento della visualizzazione mappa porta slancio e l'esempio sopra verrà attivato non appena il gesto termina ma prima che la visualizzazione mappa smetta di muoversi. Potrebbe esserci un modo migliore, ma quello che ho fatto è impostare un flag quando il gesto si interrompe readyForUpdate, e quindi controllare quel flag - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated.
tvon

14
Si noti che l'utente può toccare due volte una o due dita per eseguire lo zoom, il che cambierà la regione ma non chiamerà questo riconoscimento pan.
SomeGuy

2
Perché questa soluzione è in fondo? È il migliore! Sì , la soluzione di @mobi è più semplice ma questa è più sicura.
Leslie Godwin

77

Questo è l'unico modo che ha funzionato per me che rileva le modifiche alla panoramica e allo zoom avviate dall'utente:

- (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
Questo funziona per me, ma va notato che dipende dall'implementazione interna di MKMapViewiOS. Tale implementazione potrebbe cambiare in qualsiasi aggiornamento iOS poiché non fa parte dell'API.
programma

Funziona e mi piace di più della risposta principale perché non altera ciò che c'è.
QED

Grazie per l'elegante soluzione di manipolazione del codice e della mappa utente.
djneely

32

(Solo la) versione Swift dell'eccellente soluzione di @ 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
Sembra buono, ma ho dovuto cambiare self.mapView.subviews[0]aself.mapView.subviews[0] as! UIView
kmurph79

3
Questa soluzione (e quella di Moby) non è così eccellente. Non vi è alcuna garanzia che Apple conserverà la prima sottoview di mapViews. Forse in qualche versione futura, la prima subView di mapView non sarà una UIView. Quindi il tuo codice non è repellente ai crash. Prova ad aggiungere i tuoi GestureRecognizer a MapView.
Samet DEDE

1
nota, per farlo funzionare ho dovuto aggiungere self.map.delegate = selfa viewDidLoad
tylerSF

17

Soluzione Swift 3 alla risposta di Jano sopra:

Aggiungi il protocollo UIGestureRecognizerDelegate al tuo ViewController

class MyViewController: UIViewController, UIGestureRecognizerDelegate

Crea il UIPanGestureRecognizer viewDidLoade impostalo delegatesu self

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

Aggiungi un metodo di protocollo in modo che il tuo riconoscimento dei gesti funzioni con i gesti MKMapView esistenti

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

Aggiungi il metodo che verrà chiamato dal selettore nel tuo gesto di panoramica

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

        // do something here

    }
}

Questa è la soluzione! Grazie!
Hilalkah

8

Nella mia esperienza, simile alla "ricerca durante la digitazione", ho scoperto che un timer è la soluzione più affidabile. Elimina la necessità di aggiungere ulteriori riconoscitori di gesti per panoramica, pizzicamento, rotazione, tocco, doppio tocco, ecc.

La soluzione è semplice:

  1. Quando la regione della mappa cambia, imposta / reimposta il timer
  2. Quando il timer si attiva, carica gli indicatori per la nuova regione

    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

Un'altra possibile soluzione è implementare touchesMoved: (o touchesEnded :, ecc.) Nel controller di visualizzazione che contiene la visualizzazione della mappa, in questo modo:

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

In alcuni casi, potrebbe essere più semplice rispetto all'utilizzo dei riconoscitori di gesti.


6

Puoi anche aggiungere un riconoscimento dei gesti alla tua mappa in Interface Builder. Collegalo a una presa per la sua azione nel tuo viewController, ho chiamato il mio "mapDrag" ...

Quindi farai qualcosa del genere nel .m del tuo viewController:

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

Assicurati di avere anche questo:

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

Ovviamente dovrai rendere il tuo viewController un UIGestureRecognizerDelegate nel tuo file .h affinché funzioni.

In caso contrario, il risponditore della mappa è l'unico che ascolta l'evento del gesto.


perfetto per la soluzione storyboard. Bel lavoro conUIGestureRecognizerStateBegan
Jakub

5

Per riconoscere quando un gesto è terminato sulla visualizzazione mappa:

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

Ciò è molto utile per eseguire una query sul database solo dopo che l'utente ha eseguito lo zoom / rotazione / trascinamento della mappa.

Per me, il metodo regionDidChangeAnimated è stato chiamato solo dopo che il gesto è stato eseguito e non è stato chiamato molte volte durante il trascinamento / zoom / rotazione, ma è utile sapere se era dovuto a un gesto o meno.



Questo metodo non ha funzionato per me. Non appena la regione mapView cambia dal codice, fa scattare che proveniva da utente ...
Maksim Kniazev

5

Molte di queste soluzioni sono sul lato hacky / non quello che Swift intendeva, quindi ho optato per una soluzione più pulita.

Ho semplicemente sottoclasse MKMapView e sovrascrivo touchesMoved. Sebbene questo frammento non lo includa, consiglierei di creare un delegato o una notifica per trasmettere qualsiasi informazione tu voglia in merito al movimento.

import MapKit

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

        print("Something moved")
    }
}

Sarà necessario aggiornare la classe sui file dello storyboard in modo che punti a questa sottoclasse, nonché modificare le mappe create con altri mezzi.

Come notato nei commenti, Apple sconsiglia l'uso di sottoclassi MKMapView. Sebbene ciò sia a discrezione dello sviluppatore, questo particolare utilizzo non modifica il comportamento della mappa e ha funzionato per me senza incidenti per oltre tre anni. Tuttavia, le prestazioni passate non indicano compatibilità futura, quindi avvertenza .


1
Questa sembra la soluzione migliore. L'ho provato e sembra funzionare bene. Penso che sia bene che gli altri sappiano che Apple consiglia di non sottoclassare MKMapView: "Sebbene non si debba sottoclassare la classe MKMapView stessa, è possibile ottenere informazioni sul comportamento della vista mappa fornendo un oggetto delegato." Collegamento: developer.apple.com/documentation/mapkit/mkmapview . Tuttavia non ho una forte opinione sull'ignorare i loro consigli di non sottoclassare MKMapView e sono aperto a saperne di più dagli altri in merito.
Andrej

1
"Questa sembra la soluzione migliore anche se Apple dice di non farlo" sembra che forse non è la soluzione migliore.
dst3p

4

È possibile verificare la presenza di proprietà animate se false, quindi la mappa trascinata dall'utente

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

L'utente potrebbe aver ingrandito la mappa.
Victor Engel

3

La risposta di Jano ha funzionato per me, quindi ho pensato di lasciare una versione aggiornata per Swift 4 / XCode 9 poiché non sono particolarmente esperto in Objective C e sono sicuro che ce ne sono alcuni altri che non lo sono.

Passaggio 1: aggiungi questo codice in viewDidLoad:

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

Passaggio 2: assicurati che la tua classe sia conforme a UIGestureRecognizerDelegate:

class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UIGestureRecognizerDelegate {

Passaggio 3: aggiungi la seguente funzione per assicurarti che panGesture funzioni contemporaneamente ad altri gesti:

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

Passaggio 4: assicurandoti che il tuo metodo non sia chiamato "50 volte per trascinamento", come giustamente sottolinea Jano:

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

* Nota l'aggiunta di @objc nell'ultimo passaggio. XCode forza questo prefisso sulla tua funzione per poterla compilare.


2

So che questo è un vecchio post, ma qui il mio codice Swift 4/5 della risposta di Jano con i gesti Pan e 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
        }
    }
}

Godere!


Come detto prima, questo non riconosce gli zoom, ma dovrebbe essere Good Enough ™ per la maggior parte.
Skoua

1

Stavo cercando di avere un'annotazione al centro della mappa che è sempre al centro della mappa, indipendentemente dagli usi. Ho provato molti degli approcci sopra menzionati e nessuno di loro era abbastanza buono. Alla fine ho trovato un modo molto semplice per risolvere questo problema, prendendo in prestito dalla risposta di Anna e combinandolo con la risposta di Eneko. Fondamentalmente tratta regionWillChangeAnimated come l'inizio di un trascinamento e regionDidChangeAnimated come la fine di uno e utilizza un timer per aggiornare il pin in tempo reale:

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

inserisci il codice qui Sono riuscito a implementarlo nel modo più semplice, che gestisce tutte le interazioni con la mappa (tocco / doppio / N tocco con 1/2 / N dita, pan con 1/2 / N dita, pizzico e rotazioni

  1. Crea gesture recognizere aggiungi al contenitore della visualizzazione mappa
  2. Impostato gesture recognizer's delegatesu alcuni oggetti che implementanoUIGestureRecognizerDelegate
  3. gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch)Metodo di implementazione
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

Innanzitutto , assicurati che il tuo controller di visualizzazione corrente sia un delegato della mappa. Quindi imposta il delegato della visualizzazione mappa su self e aggiungilo MKMapViewDelegateal controller della visualizzazione. Esempio sotto.

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

E aggiungilo alla visualizzazione della mappa

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

Secondo , aggiungi questa funzione che viene attivata quando la mappa viene spostata. Filtra tutte le animazioni e si attiva solo se interagisce.

func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
   if !animated {
       // User must have dragged this, filters out all animations
       // PUT YOUR CODE HERE
   }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.