określić, czy MKMapView został przeciągnięty / przeniesiony

84

Czy istnieje sposób określenia, czy MKMapView został przeciągnięty?

Chcę uzyskać lokalizację centrum za każdym razem, gdy użytkownik przeciąga mapę, CLLocationCoordinate2D centre = [locationMap centerCoordinate];ale potrzebowałbym metody delegata lub czegoś, co uruchamia się, gdy tylko użytkownik nawiguje po mapie.

Z góry dziękuję

hgbnerd
źródło

Odpowiedzi:

22

Spójrz na odwołanie MKMapViewDelegate .

W szczególności te metody mogą być przydatne:

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

Upewnij się, że właściwość delegata widoku mapy jest ustawiona, aby te metody były wywoływane.


źródło
1
Wielkie dzięki. - (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animatedwykonał pracę.
hgbnerd
2
Świetne rozwiązanie. Idealne do ponownego ładowania adnotacji na mapie, gdy użytkownik zmienia lokalizację
Alejandro Luengo,
176
-1, ponieważ to rozwiązanie nie mówi ci, czy użytkownik przeciągnął mapę. RegionWillChangeAnimated ma miejsce, gdy użytkownik obraca urządzenie lub inna metoda powiększa mapę, niekoniecznie w odpowiedzi na przeciąganie.
CommaToast
Dzięki @CommaToast Znalazłem ten sam problem z tą „odpowiedzią”
cleverbit
3
Rozwiązanie @mobi wykrywa gesty użytkownika (tak, wszystkie), sprawdzając wewnętrzne rozpoznawanie gestów mapviews. Ładny!
Felix Alcala
236

Kod w zaakceptowanej odpowiedzi odpala, gdy region zostanie zmieniony z jakiegokolwiek powodu. Aby poprawnie wykryć przeciąganie mapy, musisz dodać UIPanGestureRecognizer. Przy okazji, to jest rozpoznawanie gestów przeciągania (przesuwanie = przeciąganie).

Krok 1: Dodaj aparat rozpoznawania gestów w widoku ViewDidLoad:

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

Krok 2: Dodaj protokół UIGestureRecognizerDelegate do kontrolera widoku, aby działał jako delegat.

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

Krok 3: I dodaj następujący kod dla UIPanGestureRecognizer do pracy z już istniejącymi aparatami rozpoznawczymi gestów w MKMapView:

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

Krok 4: jeśli chcesz wywołać swoją metodę raz zamiast 50 razy na przeciągnięcie, wykryj w selektorze stan „zakończone przeciąganie”:

- (void)didDragMap:(UIGestureRecognizer*)gestureRecognizer {
    if (gestureRecognizer.state == UIGestureRecognizerStateEnded){
        NSLog(@"drag ended");
    }
}
Jano
źródło
Wiem, że to dość stary post, ale podoba mi się twój pomysł powyżej, miałem problemy z samodzielnym zorganizowaniem mojej aplikacji metodą regionDidChange z moją implementacją, a kiedy to zobaczyłem, wszystko kliknęło i masz rację, że regionDidChange odpala z dowolnego powodu, który nie jest idealny z tym, mogę uzyskać mapę, aby zrobić dokładnie to, czego chcę, więc Brawa za to!
Alex McPherson
3
Jeśli chcesz szczypty połowów też, będziemy chcieli, aby dodać UIPinchGestureRecognizertakże
Gregory Cosmo Haun
32
Zwróć uwagę, że przewijanie widoku mapy niesie rozpęd, a powyższy przykład zostanie uruchomiony, gdy tylko gest się zakończy, ale zanim widok mapy przestanie się poruszać. Może istnieć lepszy sposób, ale zrobiłem to, ustawiając flagę, gdy gest się zatrzymuje readyForUpdate, a następnie sprawdzam tę flagę - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated.
tvon
14
Zwróć uwagę, że użytkownik może dwukrotnie dotknąć jednym lub dwoma palcami, aby powiększyć, co zmieni region, ale nie wywoła tego narzędzia do rozpoznawania panoramy.
SomeGuy,
2
Dlaczego to rozwiązanie jest na dole? To najlepszy! Tak , rozwiązanie @mobi jest prostsze, ale to jest bezpieczniejsze.
Leslie Godwin
77

To jedyny sposób, który działał dla mnie, który wykrywa zmiany panoramy i powiększenia zainicjowane przez użytkownika:

- (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
    }
}
Bałwan
źródło
2
U mnie to działa, ale należy zauważyć, że jest to zależne od wewnętrznej implementacji MKMapVieww iOS. Ta implementacja może się zmienić w każdej aktualizacji iOS, ponieważ nie jest częścią interfejsu API.
progrmr
To działa i podoba mi się bardziej niż wiodąca odpowiedź, ponieważ nie zmienia tego, co tam jest.
QED
Dzięki za eleganckie rozwiązanie manipulacji kodem i mapą użytkownika.
djneely
32

(Tylko) Szybka wersja doskonałego rozwiązania @ 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
    }
}
HEADcRASH
źródło
2
Wygląda dobrze, ale musiałem zmienić self.mapView.subviews[0]naself.mapView.subviews[0] as! UIView
kmurph79
3
To (i Moby's) rozwiązanie nie jest tak doskonałe. Nie ma gwarancji, że Apple zachowa pierwszy podwidok mapViews. Może w jakiejś przyszłej wersji pierwszy subView mapView nie będzie UIView. Więc twój kod nie jest odporny na awarie. Spróbuj dodać własne GestureRecognizers do MapView.
Samet DEDE
1
uwaga, aby to działało, musiałem dodać self.map.delegate = selfdo
widokuDidLoad
17

Szybkie 3 rozwiązanie powyższej odpowiedzi Jano :

Dodaj protokół UIGestureRecognizerDelegate do swojego ViewController

class MyViewController: UIViewController, UIGestureRecognizerDelegate

Utwórz UIPanGestureRecognizer w programie viewDidLoadi ustaw go delegatena siebie

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

Dodaj metodę protokołu, aby aparat rozpoznawania gestów działał z istniejącymi gestami MKMapView

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

Dodaj metodę, która zostanie wywołana przez selektor w geście przesuwania

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

        // do something here

    }
}
dst3p
źródło
To jest rozwiązanie! Dzięki!
Hilalkah
8

Z mojego doświadczenia wynika, że ​​podobnie jak w przypadku „wyszukiwania podczas pisania”, najbardziej niezawodnym rozwiązaniem jest minutnik. Eliminuje potrzebę dodawania dodatkowych rozpoznawania gestów do przesuwania, szczypania, obracania, stukania, podwójnego stukania itp.

Rozwiązanie jest proste:

  1. Gdy zmieni się obszar mapy, ustaw / zresetuj stoper
  2. Gdy licznik czasu zostanie uruchomiony, znaczniki wczytywania dla nowego regionu

    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
        }
    
    }
    
Eneko Alonso
źródło
7

Innym możliwym rozwiązaniem jest zaimplementowanie dotknięćMoved: (lub touchesEnded: itp.) W kontrolerze widoku, który przechowuje widok mapy, na przykład:

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

W niektórych przypadkach może to być prostsze niż używanie funkcji rozpoznawania gestów.

Aaron
źródło
6

Możesz także dodać rozpoznawanie gestów do swojej mapy w programie Interface Builder. Połącz go z gniazdem, aby wykonać jego działanie w Twoim kontrolerze viewController, nazwałem go „mapDrag” ...

Następnie zrobisz coś takiego w pliku m ViewController:

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

Upewnij się, że masz to też:

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

Oczywiście będziesz musiał ustawić viewController jako UIGestureRecognizerDelegate w swoim pliku .h, aby to zadziałało.

W przeciwnym razie osoba odpowiadająca na mapie jest jedyną osobą, która słyszy zdarzenie gestu.

CommaToast
źródło
idealny do tworzenia scenorysów. Dobra robota zUIGestureRecognizerStateBegan
Jakubem
5

Aby rozpoznać zakończenie dowolnego gestu w widoku mapy:

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

Jest to bardzo przydatne tylko do wykonywania zapytań do bazy danych po zakończeniu przez użytkownika powiększania / obracania / przeciągania mapy.

Dla mnie metoda regionDidChangeAnimated została wywołana dopiero po wykonaniu gestu i nie została wywołana wiele razy podczas przeciągania / powiększania / obracania, ale warto wiedzieć, czy było to spowodowane gestem, czy nie.

Doug Voss
źródło
Ta metoda nie zadziałała dla mnie. Jak tylko region mapView zmieni się z kodu, wyzwala, że ​​pochodzi od użytkownika ...
Maksim Kniazev
5

Wiele z tych rozwiązań jest po stronie hacky / nie tego, co zamierzał Swift, więc zdecydowałem się na bardziej przejrzyste rozwiązanie.

Po prostu podklasuję MKMapView i nadpisuję dotknięciaMoved. Chociaż ten fragment go nie zawiera, zalecałbym utworzenie delegata lub powiadomienia, aby przekazać dowolne informacje dotyczące ruchu.

import MapKit

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

        print("Something moved")
    }
}

Będziesz musiał zaktualizować klasę w swoich plikach scenorysu, aby wskazywała na tę podklasę, a także zmodyfikować wszelkie mapy utworzone w inny sposób.

Jak zauważono w komentarzach, Apple odradza stosowanie podklas MKMapView. Chociaż zależy to od uznania programisty, to szczególne użycie nie zmienia zachowania mapy i działa dla mnie bez incydentów przez ponad trzy lata. Jednak wcześniejsze wyniki nie wskazują na przyszłą kompatybilność, więc należy unikać pustki .

CodeBender
źródło
1
Wydaje się, że to najlepsze rozwiązanie. Przetestowałem to i wydaje się, że działa dobrze. Myślę, że dla innych dobrze jest wiedzieć, że Apple radzi nie tworzyć podklasy MKMapView: „Chociaż nie należy tworzyć podklasy samej klasy MKMapView, można uzyskać informacje o zachowaniu widoku mapy, udostępniając obiekt delegata”. Link: developer.apple.com/documentation/mapkit/mkmapview . Jednak nie mam mocnej opinii na temat ignorowania ich rady, aby nie tworzyć podklasy MKMapView i jestem otwarty, aby dowiedzieć się więcej na ten temat od innych.
Andrej
1
„To wydaje się być najlepszym rozwiązaniem, mimo że Apple mówi, że nie rób tego” wydaje się, że może nie jest to najlepsze rozwiązanie.
dst3p
3

Odpowiedź Jano zadziałała dla mnie, więc pomyślałem, że zostawię zaktualizowaną wersję dla Swift 4 / XCode 9, ponieważ nie jestem szczególnie biegły w Celu C i jestem pewien, że jest kilka innych, które też nie są.

Krok 1: Dodaj ten kod w viewDidLoad:

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

Krok 2. Upewnij się, że Twoja klasa jest zgodna z UIGestureRecognizerDelegate:

class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UIGestureRecognizerDelegate {

Krok 3: Dodaj następującą funkcję, aby mieć pewność, że panGesture będzie działać jednocześnie z innymi gestami:

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

Krok 4: I upewnij się, że Twoja metoda nie nazywa się „50 razy na przeciągnięcie”, jak słusznie zauważa Jano:

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

* Zwróć uwagę na dodanie @objc w ostatnim kroku. XCode wymusi ten prefiks w twojej funkcji w celu jej kompilacji.

Pigpocket
źródło
3

Możesz sprawdzić animowaną właściwość, jeśli ma wartość false, a następnie mapę przeciągniętą przez użytkownika

 func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    if animated == false {
        //user dragged map
    }
}
Roma
źródło
Użytkownik mógł powiększyć mapę.
Victor Engel
2

Wiem, że to stary post, ale tutaj mój kod Swift 4/5 odpowiedzi Jano z gestami Pan i 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
        }
    }
}

Cieszyć się!

BossOz
źródło
Jak wspomniano wcześniej, to nie rozpoznaje zoomów, ale dla większości powinno być wystarczająco dobre ™.
Skoua
1
Działa nawet w przypadku zoomów. Właśnie to przetestowałem;)
Baran Emre
1

Starałem się mieć adnotację na środku mapy, która zawsze znajduje się na środku mapy, niezależnie od tego, co robi. Wypróbowałem kilka podejść wymienionych powyżej i żadne z nich nie było wystarczająco dobre. W końcu znalazłem bardzo prosty sposób rozwiązania tego problemu, zapożyczając z odpowiedzi Anny i łącząc z odpowiedzią Eneko. Zasadniczo traktuje regionWillChangeAnimated jako początek przeciągania, a regionDidChangeAnimated jako koniec jednego i używa timera do aktualizowania pinezki w czasie rzeczywistym:

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()
}
Gadzair
źródło
0

tu wpisz kod Udało mi się to zaimplementować w najprostszy sposób, który obsługuje całą interakcję z mapą (stukanie / podwójne / N stukanie palcami 1/2 / N, przesuwanie palcami 1/2 / N, szczypanie i obroty

  1. Utwórz gesture recognizeri dodaj do kontenera widoku mapy
  2. Ustaw gesture recognizer's delegatena implementację jakiegoś obiektuUIGestureRecognizerDelegate
  3. Implementuj gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch)metodę
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
}
Nikita Ivaniushchenko
źródło
-1

Najpierw upewnij się, że Twój bieżący kontroler widoku jest delegatem mapy. Ustaw więc delegata Map View na siebie i dodaj MKMapViewDelegatedo kontrolera widoku. Przykład poniżej.

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

I dodaj to do widoku mapy

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

Po drugie , dodaj tę funkcję, która jest uruchamiana, gdy mapa jest przesuwana. Odfiltruje wszelkie animacje i uruchomi się tylko w przypadku interakcji.

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