Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift GestureRecognizers with MKMapKit - Drop pin and drag

I am trying to use a number of UIGestureReconizers with an MKMapView onto which the user can drop a pin and drag it around. It has a callout. I am implementing this in a TabbarController and also within a NavigationController. I currently have:

1) A PanGestureRecognizer animates the Tabbar and Navigation item off the screen. This works fine without interfering with panning the map.

2) A TapGestureRecognizer set to one tap animates the two items from 1) back onto the screen.

3) A TapGestureRecognizer set to two taps allows the underlying MKMapView zoom functionality to work. This GestureRecognizer's delegate has gestureRecognizer.shouldRecognizeSimultaneouslyWithGestureRecognizer set to true

These are setup in viewDidLoad as follows:

 // This sets up the pan gesture recognizer to hide the bars from the UI.
    let panRec: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: "didDragMap:")
    panRec.delegate = self
    mapView.addGestureRecognizer(panRec)

    // This sets up the tap gesture recognizer to un-hide the bars from the UI.
    let singleTap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: "didTapMap:")
    singleTap.delegate = self
    singleTap.numberOfTapsRequired = 1
    singleTap.numberOfTouchesRequired = 1
    mapView.addGestureRecognizer(singleTap)

    // This sets up the double tap gesture recognizer to enable the zoom facility.
    // In order to pass double-taps to the underlying `MKMapView` the delegate for this recognizer (self) needs to return true from
    // gestureRecognizer.shouldRecognizeSimultaneouslyWithGestureRecognizer
    let doubleTap: UITapGestureRecognizer = UITapGestureRecognizer()
    doubleTap.numberOfTapsRequired = 2
    doubleTap.numberOfTouchesRequired = 1
    doubleTap.delegate = self
    mapView.addGestureRecognizer(doubleTap)

// This delays the single-tap recognizer slightly and ensures that it will NOT fire if there is a double-tap
    singleTap.requireGestureRecognizerToFail(doubleTap)

My problem occurs when I try to implement a UILongPressGestureRecognizer to allow the dropping of a pin onto the map. I'm trying to use the following added to viewDidLoad:

// This sets up the long tap to drop the pin.
    let longTap: UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: "didLongTapMap:")
    longTap.delegate = self
    longTap.numberOfTapsRequired = 0
    longTap.minimumPressDuration = 0.5
    mapView.addGestureRecognizer(longTap)

This is my action method:

func didLongTapMap(gestureRecognizer: UIGestureRecognizer) {
    // Get the spot that was tapped.
    let tapPoint: CGPoint = gestureRecognizer.locationInView(mapView)
    let touchMapCoordinate: CLLocationCoordinate2D = mapView.convertPoint(tapPoint, toCoordinateFromView: mapView)

    var viewAtBottomOfHierarchy: UIView = mapView.hitTest(tapPoint, withEvent: nil)
    if let viewAtBottom = viewAtBottomOfHierarchy as? MKPinAnnotationView {
        return
    } else {
        if .Began == gestureRecognizer.state {
            // Delete any existing annotations.
            if mapView.annotations.count != 0 {
                mapView.removeAnnotations(mapView.annotations)
            }

            annotation = MKPointAnnotation()
            annotation.coordinate = touchMapCoordinate

            mapView.addAnnotation(annotation)
            _isPinOnMap = true

            findAddressFromCoordinate(annotation.coordinate)
            updateLabels()
        }
    }
}

This does indeed allow a pin to be dropped on a long tap and a single tap will display the callout BUT a second tap to hold and drag causes a second pin to drop if the drag isn't started sufficiently quickly. This second pin drops into the space the previous pin was hovering in and can be dragged by the user, but the new pin dropping is awkward and wrong looking.

I'm trying to use the line:

if let viewAtBottom = viewAtBottomOfHierarchy as? MKPinAnnotationView {

to return the tap to the MKMapView and prevent another pin being dropped but the return never gets called even though a breakpoint on this line shows viewAtBottom is of type MapKit.MKPinAnnotationView. Any ideas where I'm going wrong?

like image 367
Magnas Avatar asked Jul 16 '14 10:07

Magnas


1 Answers

I think I might have the answer to your problem if I understood it correctly. Your having problems when one pin is dropped and then dragging the screen around without placing another pin, correct? This is my code, I have been making something similar and this seems to work for me. import UIKit import MapKit import CoreLocation

class ViewController: UIViewController, MKMapViewDelegate, CLLocationManagerDelegate {

    @IBOutlet var map: MKMapView!

    var manager = CLLocationManager()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.


        let uilpgr = UILongPressGestureRecognizer(target: self, action: #selector(ViewController.longpress(gestureRecognizer:)))

        uilpgr.minimumPressDuration = 2

        map.addGestureRecognizer(uilpgr)

        if activePlace == -1 {

            manager.delegate = self
            manager.desiredAccuracy = kCLLocationAccuracyBest
            manager.requestWhenInUseAuthorization()
            manager.startUpdatingLocation()
            self.map.showsUserLocation = true

        } else {

            //GET PLACE DETAILS TO DISPLAY ON MAP

            if places.count > activePlace {

                if let name = places[activePlace]["name"]{

                    if let lat = places[activePlace]["lat"]{

                        if let lon = places[activePlace]["lon"]{

                            if let latitude = Double(lat) {

                                if let longitude = Double(lon) {

                                    let span = MKCoordinateSpan(latitudeDelta:  0.05, longitudeDelta: 0.05)

                                    let coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)

                                    let region = MKCoordinateRegionMake(coordinate, span)

                                    self.map.setRegion(region, animated: true)

                                    let annotation = MKPointAnnotation()

                                    annotation.coordinate = coordinate

                                    annotation.title = name

                                    self.map.addAnnotation(annotation)

                                }

                            }

                        }

                    }

                }

            }

        }


    }


    func longpress(gestureRecognizer: UIGestureRecognizer) {

        if gestureRecognizer.state == UIGestureRecognizerState.began {

            let touchPoint = gestureRecognizer.location(in: self.map)

            let newCoordinate = self.map.convert(touchPoint, toCoordinateFrom: self.map)

            let location = CLLocation(latitude: newCoordinate.latitude, longitude: newCoordinate.longitude)

            var title = ""

            CLGeocoder().reverseGeocodeLocation(location, completionHandler: { (placemarks, error) in

                if error != nil {

                    print(error)

                } else {

                    if let placemark = placemarks?[0] {

                        if placemark.subThoroughfare != nil {

                            title += placemark.subThoroughfare! + " "

                        }

                        if placemark.thoroughfare != nil {

                            title += placemark.thoroughfare! + " "

                        }



                    }

                }

                if title == "" {

                    title = "Added \(NSDate())"

                }

                let annotation = MKPointAnnotation()

                annotation.coordinate = newCoordinate

                annotation.title = title

                self.map.addAnnotation(annotation)

                places.append(["name": title, "lat":String(newCoordinate.latitude), "lon":String(newCoordinate.longitude)])

                UserDefaults.standard.set(places, forKey: "places")

            })

        }

    }

    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {

            let location = CLLocationCoordinate2D(latitude: locations[0].coordinate.latitude, longitude: locations[0].coordinate.longitude)

            let span = MKCoordinateSpan(latitudeDelta: 0.05, longitudeDelta: 0.05)

            let region = MKCoordinateRegion(center: location, span: span)

            self.map.setRegion(region, animated: true)

    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}

Hope it helps, the problem might also be that your minimum longpress duration is only 0.5.

like image 193
Øystein Vaagen Avatar answered Oct 25 '22 15:10

Øystein Vaagen