web-dev-qa-db-fra.com

déterminer si MKMapView a été glissé/déplacé

Existe-t-il un moyen de déterminer si une MKMapView a été déplacée?

Je souhaite connaître l'emplacement du centre à chaque fois qu'un utilisateur dessine la carte à l'aide de CLLocationCoordinate2D centre = [locationMap centerCoordinate];, mais il me faudrait une méthode de délégation ou quelque chose qui se déclenche dès que l'utilisateur navigue avec la carte.

Merci d'avance

73
hgbnerd

Regardez le MKMapViewDelegate reference. 

Plus précisément, ces méthodes peuvent être utiles:

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

Assurez-vous que la propriété de délégué de votre vue Carte est définie pour que ces méthodes soient appelées.

28
user467105

Le code de la réponse acceptée est déclenché lorsque la région est modifiée pour une raison quelconque. Pour détecter correctement une carte glissée, vous devez ajouter un UIPanGestureRecognizer. Btw, il s'agit de la reconnaissance de mouvements par glisser-déposer (panoramique = glisser).

Étape 1: Ajoutez le programme de reconnaissance de mouvements dans viewDidLoad:

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

Étape 2: / Ajoutez le protocole UIGestureRecognizerDelegate au contrôleur de vue pour qu’il fonctionne en tant que délégué.

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

Étape 3: Et ajoutez le code suivant pour que UIPanGestureRecognizer fonctionne avec les dispositifs de reconnaissance de mouvements déjà existants dans MKMapView:

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

Étape 4: Si vous souhaitez appeler votre méthode une fois au lieu de 50 fois par glissement, détectez l'état "glisser terminé" dans votre sélecteur:

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

C’est la seule façon qui a fonctionné pour moi de détecter les modifications du panoramique et du zoom initiées par l’utilisateur:

- (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
    }
}
69
Snowman

(Juste la) version rapide de l'excellente solution 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
    }
}
25
hEADcRASH

Swift 3 solution à Réponse de Jano ci-dessus:

Ajouter le protocole UIGestureRecognizerDelegate à votre ViewController

class MyViewController: UIViewController, UIGestureRecognizerDelegate

Créez le UIPanGestureRecognizer dans viewDidLoad et définissez delegate sur 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)
}

Ajouter une méthode de protocole pour que votre reconnaissance de mouvements fonctionne avec les mouvements MKMapView existants

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

Ajouter la méthode qui sera appelée par le sélecteur dans votre geste panoramique

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

        // do something here

    }
}
9
dst3p

Dans mon expérience, similaire à "rechercher lors de la frappe", j'ai trouvé une minuterie est la solution la plus fiable. Il supprime la nécessité d'ajouter des dispositifs de reconnaissance de geste supplémentaires pour le panoramique, le pincement, la rotation, la frappe, la double frappe, etc.

La solution est simple:

  1. Lorsque la région de la carte change, réglez/réinitialisez le chronomètre
  2. Lorsque la minuterie se déclenche, chargez les marqueurs pour la nouvelle région

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

Vous pouvez également ajouter un identificateur de mouvements à votre carte dans Interface Builder. Liez-le à un point de vente pour son action dans votre vueContrôleur, j'ai appelé le mien "mapDrag" ... 

Ensuite, vous ferez quelque chose comme ceci dans le fichier .m de votre viewController:

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

Assurez-vous d'avoir ceci aussi

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

Pour que cela fonctionne, vous devez bien sûr faire de votre viewController un UIGestureRecognizerDelegate.

Sinon, le répondeur de la carte est le seul à entendre l'événement de geste. 

6
CommaToast

Une autre solution possible consiste à implémenter touchesMoved: (ou touchesEnded:, etc.) dans le contrôleur de vue contenant la vue de la carte, comme suit:

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

Cela peut être plus simple que d’utiliser des reconnaisseurs de geste, dans certains cas.

6
Aaron

Pour reconnaître la fin d’un geste sur la vue cartographique:

http://b2cloud.com.au/tutorial/mkmapview-determining-whether-region-change-is-from-user-interaction/

Ceci est très utile pour effectuer uniquement une requête sur la base de données une fois que l'utilisateur a terminé le zoom/la rotation/le déplacement de la carte.

Pour moi, la méthode regionDidChangeAnimated n'a été appelée qu'après le geste, et n'a pas été appelée plusieurs fois en faisant glisser/zoomer/pivoter, mais il est utile de savoir si c'était dû à un geste ou non.

3
Doug Voss

Vous pouvez rechercher une propriété animée Si la valeur est false, l'utilisateur a fait glisser la carte. 

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

La réponse de Jano a fonctionné pour moi, alors j'ai pensé laisser une version mise à jour pour Swift 4/XCode 9, car je ne suis pas particulièrement compétent en objectif C et je suis sûr qu'il y en a quelques autres qui ne le sont pas non plus.

Étape 1: Ajoutez ce code dans viewDidLoad:

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

Étape 2: Assurez-vous que votre classe est conforme à UIGestureRecognizerDelegate:

class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UIGestureRecognizerDelegate {

Étape 3: / Ajoutez la fonction suivante pour vous assurer que votre panGesture fonctionnera simultanément avec les autres gestes:

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

Étape 4: Et assurez-vous que votre méthode n'est pas appelée "50 fois par traînée" comme Jano le fait remarquer à juste titre:

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

* Notez l'ajout de @objc à la dernière étape. XCode forcera ce préfixe sur votre fonction pour la compilation.

3
Pigpocket

Beaucoup de ces solutions sont du côté du hacky/pas ce que Swift avait l'intention de faire, alors j'ai opté pour une solution plus propre.

Je sous-classe simplement MKMapView et substitue touchesMoved. Bien que cet extrait ne l'inclue pas, je vous recommande de créer un délégué ou une notification pour transmettre les informations de votre choix concernant le mouvement.

import MapKit

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

        print("Something moved")
    }
}

Vous devrez mettre à jour la classe sur vos fichiers de storyboard pour qu'elle pointe vers cette sous-classe, ainsi que modifier les cartes que vous créez par d'autres moyens.

3
CodeBender

Je sais que ceci est un ancien post mais voici mon Swift 4/5 code de réponse de Jano avec les gestes de Pan et 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
        }
    }
}

Prendre plaisir!

0
BossOz

entrer le code iciJ'ai réussi à le mettre en œuvre de la manière la plus simple, qui gère toutes les interactions avec la carte (tapotement/double/tapotement avec N doigts avec 1/2/N doigts, panoramique avec doigts avec 1/2/N, pincement et rotations

  1. Créer gesture recognizer et ajouter au conteneur de la vue cartographique
  2. Définissez gesture recognizer'sdelegate sur un objet implémentant UIGestureRecognizerDelegate
  3. Implémenter la méthode 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
}
0

Premièrement , assurez-vous que votre contrôleur de vue actuel est un délégué de la carte. Définissez donc votre délégué Vue de carte sur auto et ajoutez MKMapViewDelegate à votre contrôleur de vue. Exemple ci-dessous.

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

Et ajoutez ceci à la vue de votre carte

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

Deuxièmement , ajoutez cette fonction qui est activée lorsque la carte est déplacée. Il filtrera toutes les animations et ne se déclenchera que s'il y a interaction.

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

J'essayais d'avoir une annotation au centre de la carte qui est toujours au centre de la carte, quelles que soient les utilisations. J'ai essayé plusieurs des approches mentionnées ci-dessus, et aucune d'entre elles n'était assez bonne. J'ai finalement trouvé un moyen très simple de résoudre ce problème, empruntant à la réponse d'Anna et combinant celle d'Eneko. Fondamentalement, il considère régionWillChangeAnimated comme le début d'un glissement, et regionDidChangeAnimated comme la fin d'un, et utilise une minuterie pour mettre à jour l'épingle en temps réel:

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
Gadzair