Bestimmen Sie, ob MKMapView gezogen / verschoben wurde

84

Gibt es eine Möglichkeit festzustellen, ob ein MKMapView herumgeschleppt wurde?

Ich möchte den zentralen Standort jedes Mal ermitteln, wenn ein Benutzer die Karte mit zieht, CLLocationCoordinate2D centre = [locationMap centerCoordinate];aber ich benötige eine Delegierungsmethode oder etwas, das ausgelöst wird, sobald der Benutzer mit der Karte navigiert.

Danke im Voraus

hgbnerd
quelle

Antworten:

22

Schauen Sie sich die MKMapViewDelegate- Referenz an.

Insbesondere können diese Methoden nützlich sein:

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

Stellen Sie sicher, dass die Delegate-Eigenschaft Ihrer Kartenansicht festgelegt ist, damit diese Methoden aufgerufen werden.


quelle
1
Vielen Dank. - (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animatedhat den Job gemacht.
Hgbnerd
2
Tolle Lösung. Perfekt, um Anmerkungen auf der Karte neu zu laden, wenn der Benutzer den Standort wechselt
Alejandro Luengo
173
-1, da diese Lösung Ihnen nicht sagt, ob der Benutzer die Karte gezogen hat. Die RegionWillChangeAnimated tritt auf, wenn der Benutzer das Gerät dreht oder eine andere Methode die Karte zoomt, nicht unbedingt als Reaktion auf das Ziehen.
CommaToast
Danke @CommaToast Ich habe das gleiche Problem mit dieser 'Antwort' gefunden
Cleverbit
3
Die Lösung von @mobi erkennt Benutzergesten (ja, alle), indem sie die internen Gestenerkenner von mapviews überprüft. Nett!
Felix Alcala
236

Der Code in der akzeptierten Antwort wird ausgelöst, wenn die Region aus irgendeinem Grund geändert wird. Um einen Kartenzug richtig zu erkennen, müssen Sie einen UIPanGestureRecognizer hinzufügen. Übrigens ist dies die Erkennung von Drag-Gesten (Schwenken = Ziehen).

Schritt 1: Fügen Sie den Gestenerkenner in viewDidLoad hinzu:

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

Schritt 2: Fügen Sie das Protokoll UIGestureRecognizerDelegate zum Ansichtscontroller hinzu, damit es als Delegat fungiert.

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

Schritt 3: Fügen Sie den folgenden Code für den UIPanGestureRecognizer hinzu, um mit den bereits vorhandenen Gestenerkennern in MKMapView zu arbeiten:

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

Schritt 4: Wenn Sie Ihre Methode einmal statt 50 Mal pro Drag aufrufen möchten, erkennen Sie den Status "Drag beendet" in Ihrer Auswahl:

- (void)didDragMap:(UIGestureRecognizer*)gestureRecognizer {
    if (gestureRecognizer.state == UIGestureRecognizerStateEnded){
        NSLog(@"drag ended");
    }
}
Jano
quelle
Ich weiß, dass dies ein ziemlich alter Beitrag ist, aber ich liebe Ihre Idee oben. Ich hatte Probleme, meine App mit der regionDidChange-Methode selbst zu organisieren, und als ich das sah, hat alles geklickt und Sie haben so Recht, dass regionDidChange aus irgendeinem Grund ausgelöst wird ist nicht ideal damit kann ich vielleicht map bekommen um genau das zu tun was ich will also ein dickes Lob dafür!
Alex McPherson
3
Wenn auch Sie zu fangen kneift wollen, sollten Sie eine hinzufügen UIPinchGestureRecognizerals auch
Gregory Cosmo Haun
32
Beachten Sie, dass der Bildlauf der Kartenansicht einen Impuls enthält und das obige Beispiel ausgelöst wird, sobald die Geste endet, aber bevor sich die Kartenansicht nicht mehr bewegt. Es mag einen besseren Weg geben, aber ich habe ein Flag gesetzt, wenn die Geste stoppt readyForUpdate, und dieses Flag dann eingecheckt - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated.
Am
14
Beachten Sie, dass der Benutzer zweimal auf einen oder zwei Finger tippen kann, um zu zoomen. Dadurch wird der Bereich geändert, dieser Schwenkerkenner wird jedoch nicht aufgerufen.
SomeGuy
2
Warum ist diese Lösung unten? Es ist das beste! Ja , die Lösung von @mobi ist einfacher, aber diese ist sicherer.
Leslie Godwin
77

Dies ist die einzige für mich funktionierende Methode, die vom Benutzer initiierte Schwenk- und Zoomänderungen erkennt:

- (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
    }
}
Schneemann
quelle
2
Dies funktioniert für mich, aber es sollte beachtet werden, dass dies von der internen Implementierung MKMapViewin iOS abhängt . Diese Implementierung kann sich in jedem iOS-Update ändern, da es nicht Teil der API ist.
Programm
Das funktioniert und ich mag es besser als die führende Antwort, weil es nichts daran ändert, was da ist.
QED
Vielen Dank für die elegante Lösung der Manipulation von Code und Benutzerkarte.
djneely
32

(Nur die) Schnelle Version von @mobis hervorragender Lösung :

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
    }
}
Head-Crash
quelle
2
Sieht gut aus, aber ich musste self.mapView.subviews[0]umself.mapView.subviews[0] as! UIView
kmurph79
3
Diese (und Mobys) Lösung ist nicht so hervorragend. Es gibt keine Garantie dafür, dass Apple die erste Unteransicht von mapViews beibehält. Möglicherweise wird in einer zukünftigen Version die erste Unteransicht von mapView keine UIView sein. Ihr Code ist also nicht abstoßabweisend. Versuchen Sie, MapView eigene GestureRecognizers hinzuzufügen.
Samet DEDE
1
Beachten Sie, um dies self.map.delegate = selfzum Laufen zu bringen, musste ich zu viewDidLoad
tylerSF
16

Swift 3-Lösung für Janos Antwort oben:

Fügen Sie das Protokoll UIGestureRecognizerDelegate zu Ihrem ViewController hinzu

class MyViewController: UIViewController, UIGestureRecognizerDelegate

Erstellen Sie den UIPanGestureRecognizer in viewDidLoadund setzen Sie ihn delegateauf 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)
}

Fügen Sie eine Protokollmethode hinzu, damit Ihre Gestenerkennung mit den vorhandenen MKMapView-Gesten funktioniert

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

Fügen Sie in Ihrer Schwenkgeste die Methode hinzu, die vom Selektor aufgerufen wird

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

        // do something here

    }
}
dst3p
quelle
Das ist die Lösung! Vielen Dank!
Hilalkah
8

Nach meiner Erfahrung, ähnlich wie "Suchen während des Tippens", fand ich, dass ein Timer die zuverlässigste Lösung ist. Es müssen keine zusätzlichen Gestenerkenner zum Schwenken, Kneifen, Drehen, Antippen, Doppeltippen usw. hinzugefügt werden.

Die Lösung ist einfach:

  1. Wenn sich der Kartenbereich ändert, stellen Sie den Timer ein / setzen Sie ihn zurück
  2. Wenn der Timer ausgelöst wird, laden Sie Markierungen für die neue Region

    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
quelle
7

Eine andere mögliche Lösung besteht darin, touchMoved: (oder touchEnded: usw.) in den Ansichts-Controller zu implementieren, der Ihre Kartenansicht enthält, wie folgt:

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

Dies kann in einigen Fällen einfacher sein als die Verwendung von Gestenerkennern.

Aaron
quelle
6

Sie können Ihrer Karte im Interface Builder auch eine Gestenerkennung hinzufügen. Verbinde es mit einer Steckdose für seine Aktion in deinem viewController, ich habe meine "mapDrag" genannt ...

Dann machen Sie so etwas in der .m: Ihres viewController:

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

Stellen Sie sicher, dass Sie dies auch dort haben:

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

Natürlich müssen Sie Ihren viewController zu einem UIGestureRecognizerDelegate in Ihrer .h-Datei machen, damit dies funktioniert.

Andernfalls ist der Responder der Karte der einzige, der das Gestenereignis hört.

CommaToast
quelle
Perfekt für Storyboard-Lösungen. Gute Arbeit mitUIGestureRecognizerStateBegan
Jakub
5

So erkennen Sie, wann eine Geste in der Kartenansicht beendet wurde:

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

Dies ist sehr nützlich, um eine Datenbankabfrage erst durchzuführen, nachdem der Benutzer die Karte gezoomt / gedreht / gezogen hat.

Für mich wurde die regionDidChangeAnimated-Methode erst aufgerufen, nachdem die Geste ausgeführt wurde, und wurde beim Ziehen / Zoomen / Drehen nicht oft aufgerufen. Es ist jedoch hilfreich zu wissen, ob dies auf eine Geste zurückzuführen ist oder nicht.

Doug Voss
quelle
Diese Methode hat bei mir nicht funktioniert. Sobald sich die mapView-Region vom Code ändert, wird ausgelöst, dass sie vom Benutzer stammt ...
Maksim Kniazev
5

Viele dieser Lösungen sind auf der hackigen / nicht der von Swift beabsichtigten Seite, daher habe ich mich für eine sauberere Lösung entschieden.

Ich unterteile MKMapView einfach und überschreibe touchMoved. Obwohl dieses Snippet es nicht enthält, würde ich empfehlen, einen Delegaten oder eine Benachrichtigung zu erstellen, um die gewünschten Informationen über die Bewegung weiterzuleiten.

import MapKit

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

        print("Something moved")
    }
}

Sie müssen die Klasse in Ihren Storyboard-Dateien aktualisieren, um auf diese Unterklasse zu verweisen, und alle Karten ändern, die Sie auf andere Weise erstellen.

Wie in den Kommentaren erwähnt, rät Apple von der Verwendung von Unterklassen ab MKMapView. Obwohl dies im Ermessen des Entwicklers liegt, ändert diese spezielle Verwendung das Verhalten der Karte nicht und funktioniert seit über drei Jahren ohne Zwischenfälle für mich. Allerdings ist Vergangenheit erzielte Ergebnisse nicht zeigen zukünftige Kompatibilität, so Ausschluss jeder Haftung .

CodeBender
quelle
1
Dies scheint die beste Lösung zu sein. Ich habe es getestet und es scheint in Ordnung zu funktionieren. Ich denke, es ist gut für andere, sich darüber im Klaren zu sein, dass Apple davon abrät, MKMapView nicht zu unterordnen: "Obwohl Sie die MKMapView-Klasse selbst nicht unterordnen sollten, können Sie Informationen zum Verhalten der Kartenansicht erhalten, indem Sie ein Delegatenobjekt bereitstellen." Link: developer.apple.com/documentation/mapkit/mkmapview . Ich habe jedoch keine feste Meinung darüber, ihren Rat zu ignorieren, MKMapView nicht zu unterordnen, und ich bin offen, diesbezüglich mehr von anderen zu lernen.
Andrej
1
"Dies scheint die beste Lösung zu sein, obwohl Apple sagt, dass Sie es nicht tun sollen", scheint es vielleicht nicht die beste Lösung zu sein.
dst3p
3

Janos Antwort hat bei mir funktioniert, daher dachte ich, ich würde eine aktualisierte Version für Swift 4 / XCode 9 hinterlassen, da ich Ziel C nicht besonders gut beherrsche und ich bin sicher, dass es einige andere gibt, die dies auch nicht sind.

Schritt 1: Fügen Sie diesen Code in viewDidLoad hinzu:

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

Schritt 2: Stellen Sie sicher, dass Ihre Klasse dem UIGestureRecognizerDelegate entspricht:

class MapViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate, UIGestureRecognizerDelegate {

Schritt 3: Fügen Sie die folgende Funktion hinzu, um sicherzustellen, dass Ihre panGesture gleichzeitig mit anderen Gesten funktioniert:

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

Schritt 4: Und stellen Sie sicher, dass Ihre Methode nicht "50 Mal pro Drag" genannt wird, wie Jano zu Recht betont:

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

* Beachten Sie das Hinzufügen von @objc im letzten Schritt. XCode erzwingt dieses Präfix für Ihre Funktion, damit es kompiliert werden kann.

Pigpocket
quelle
3

Sie können nach animierten Eigenschaften suchen, wenn diese falsch sind, und dann die vom Benutzer gezogene Karte

 func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    if animated == false {
        //user dragged map
    }
}
Roma
quelle
Benutzer könnte Karte gezoomt haben.
Victor Engel
1

Ich habe versucht, eine Anmerkung in der Mitte der Karte zu haben, die sich immer in der Mitte der Karte befindet, unabhängig davon, was die Verwendung bewirkt. Ich habe mehrere der oben genannten Ansätze ausprobiert, und keiner von ihnen war gut genug. Ich fand schließlich einen sehr einfachen Weg, dies zu lösen, indem ich mich an die Antwort von Anna anlehnte und sie mit Enekos Antwort kombinierte. Grundsätzlich wird regionWillChangeAnimated als Beginn eines Ziehens und regionDidChangeAnimated als Ende eines Ziehvorgangs behandelt und der Pin mithilfe eines Timers in Echtzeit aktualisiert:

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
quelle
1

Ich weiß, dass dies ein alter Beitrag ist, aber hier mein Swift 4/5-Code von Janos Antwort mit Pan- und Pinch-Gesten.

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

Genießen!

BossOz
quelle
Wie bereits erwähnt, werden Zooms nicht erkannt, aber für die meisten sollte es gut genug sein.
Skoua
0

Code hier eingebenIch habe es geschafft, dies auf einfachste Weise zu implementieren, die alle Interaktionen mit der Karte handhabt (Tippen / Doppel / N Tippen mit 1/2 / N Fingern, Schwenken mit 1/2 / N Fingern, Kneifen und Drehen

  1. Erstellen gesture recognizerSie den Container der Kartenansicht und fügen Sie ihn hinzu
  2. Auf gesture recognizer's delegateein Objekt setzen, das implementiert wirdUIGestureRecognizerDelegate
  3. gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch)Methode implementieren
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
quelle
-1

Erstens , stellen Sie sicher , dass Ihre aktuellen View - Controller ein Delegierter der Karte ist. Stellen Sie Ihren Map View-Delegaten auf self ein und fügen Sie ihn MKMapViewDelegateIhrem View Controller hinzu. Beispiel unten.

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

Und fügen Sie dies Ihrer Kartenansicht hinzu

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

Zweitens fügen Sie diese Funktion hinzu, die ausgelöst wird, wenn die Karte verschoben wird. Es filtert alle Animationen heraus und wird nur ausgelöst, wenn mit ihnen interagiert wird.

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