Is it possible to have a point annotation view that can be dragged OR selected?

437 views Asked by At

I have a MKMapView that shows some MKPinAnnotationView objects.
I want to be able to drag an annotation view, but I want also to be able to select it.

The problem:

When I implement the delegate function

func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {…}  

this function is immediately called when I touch down to the annotation view. It shows an alert, and thus prevents the annotation view from dragging.

When I do not implement the delegate function, I can drag the annotation view as expected.

I think the following should be possible:
- Touch down to the annotation view.
- When I drag, move the annotation view.
- Else, i.e. when I touch up from the annotation view, select it.

How can I achieve this?

EDIT:

My pin annotation view does not have a callout. Rather, when it is selected, it shows an UIAlertController so that the user can choose further actions. If so, the mapView is dimmed an is not accessible.

The behaviour that I want to implement is:

If i touch the pin annotation view (finger down), nothing should happen.

If I then move the finger (still down), the pin annotation view should be dragged. If I then lift the finger, the pin annotation view will not be selected.

If, however, I do not mode the finger, but simply lift it, The pin annotation view will be selected (and the alert view should be shown).

I hope this clarifies the situation.

1

There are 1 answers

2
Ryan H. On BEST ANSWER

One possible solution is to handle the dragging yourself and utilize a long press gesture to manage the actions you desire (such as when to display an alert).

This solution is built around Rob's detailed answer here and adds some additional logic to handle the specifics of your question (namely the wasMoved instance property).

private var startLocation = CGPoint(x: 0.0, y: 0.0)
private var wasMoved = false

func handleLongPress(_ sender: UILongPressGestureRecognizer) {
    let location = sender.location(in: mapView)

    switch sender.state {
    case .began:
        startLocation = location
    case .changed:
        wasMoved = true
        sender.view?.transform = CGAffineTransform(translationX: location.x - startLocation.x, y: location.y - startLocation.y)
    case .ended, .cancelled:
        if wasMoved {
            let annotationView = sender.view as! MKAnnotationView
            let annotation = annotationView.annotation as! MKPointAnnotation

            let translate = CGPoint(x: location.x - startLocation.x, y: location.y - startLocation.y)
            let originalLocation = mapView.convert(annotation.coordinate, toPointTo: mapView)
            let updatedLocation = CGPoint(x: originalLocation.x + translate.x, y: originalLocation.y + translate.y)

            annotationView.transform = CGAffineTransform.identity
            annotation.coordinate = mapView.convert(updatedLocation, toCoordinateFrom: mapView)
        } else {
            let alert = UIAlertController(title: "Alert", message: "Here is my alert!", preferredStyle: .alert)

            let ok = UIAlertAction(title: "OK", style: .default, handler: nil)
            alert.addAction(ok)

            present(alert, animated: true, completion: nil)
        }
        wasMoved = false
    default:
        break
    }
}

And your mapView(_:viewFor:) delegate method would look like:

extension ViewController: MKMapViewDelegate {
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
        guard let reusablePin = mapView.dequeueReusableAnnotationView(withIdentifier: "Pin") as? MKPinAnnotationView else {
            let pin = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "Pin")

            // Add the long press gesture recognizer to the annotation view
            let longPress = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress(_:)))
            longPress.minimumPressDuration = 0
            pin.addGestureRecognizer(longPress)

            return pin
        }

        reusablePin.annotation = annotation
        return reusablePin
    }
}