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.