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
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.
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");
}
}
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
}
}
(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
}
}
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
}
}
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:
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
}
}
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.
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.
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.
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
}
}
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.
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.
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!
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
gesture recognizer
et ajouter au conteneur de la vue cartographiquegesture recognizer's
delegate
sur un objet implémentant UIGestureRecognizerDelegate
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 }
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
}
}
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()
}