Adrian Le Roy Devezin
Adrian Le Roy Devezin

Reputation: 843

Detect longpress in MapView for SwiftUI

I have a MapView in SwiftUi and I am trying to add a pin annotation to it when a user long presses a location on the map. I see this can easily be accomplished in swift however I am using SwiftUI. I do not know how to add the long-press detector. A code example would be great.

My MapView

struct MapView: UIViewRepresentable {

@Binding
var annotations: [PinAnnotation]
let addAnnotationListener: (PinAnnotation) -> Void

func makeUIView(context: Context) -> MKMapView {
    let mapView = MKMapView()
    mapView.delegate = context.coordinator
    return mapView
}

func updateUIView(_ view: MKMapView, context: Context) {
    view.delegate = context.coordinator
    view.addAnnotations(annotations)
    if annotations.count == 1 {
        let coords = annotations.first!.coordinate
        let region = MKCoordinateRegion(center: coords, span: MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1))
        view.setRegion(region, animated: true)
    }

}


func makeCoordinator() -> MapViewCoordinator {
    MapViewCoordinator(self)
}

}

MapViewCoordinator

class MapViewCoordinator: NSObject, MKMapViewDelegate {

var mapViewController: MapView

init(_ control: MapView) {
    self.mapViewController = control
}

func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
    let annotation = view.annotation
    guard let placemark = annotation as? MKPointAnnotation else { return }
}

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?{
    //Custom View for Annotation
    let identifier = "Placemark"
    if  let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) {
        annotationView.annotation = annotation
        return annotationView
    } else {
        let annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
        annotationView.isEnabled = true
        annotationView.canShowCallout = true
        let button = UIButton(type: .infoDark)
        annotationView.rightCalloutAccessoryView = button
        return annotationView
    }
}
}

The method to add a pin to a MapView

    func addPinBasedOnGesture(gestureRecognizer:UIGestureRecognizer){
        var touchPoint = gestureRecognizer.locationInView(mapView)
        var newCoordinates = self.convertPoint(touchPoint, toCoordinateFromView: mapView)
        let annotation = MKPointAnnotation()
        annotation.coordinate = newCoordinates
        mapView.addAnnotation(annotation)
    }

Upvotes: 4

Views: 1621

Answers (3)

Vicente Garcia
Vicente Garcia

Reputation: 6380

iOS 18

The proposed solutions here are great, but if you can target iOS 18+ SwiftUI now supports this, with UIGestureRecognizerRepresentable that can use any UIGestureRecognizer, like UILongPressGestureRecognizer.

Example

Map with MapReader

MapReader { proxy in
    Map()
        .gesture(LongPressGesture { position in
            let coordinate = proxy.convert(position, from: .global)
            /// add pin or annotation
        })
}

Gesture Recognizer

import SwiftUI

struct LongPressGesture: UIGestureRecognizerRepresentable {
    private let longPressAt: (_ position: CGPoint) -> Void
    
    init(longPressAt: @escaping (_ position: CGPoint) -> Void) {
        self.longPressAt = longPressAt
    }
    
    func makeUIGestureRecognizer(context: Context) -> UILongPressGestureRecognizer {
        UILongPressGestureRecognizer()
    }
    
    func handleUIGestureRecognizerAction(_ gesture: UILongPressGestureRecognizer, context: Context) {
        guard  gesture.state == .began else { return }
        longPressAt(gesture.location(in: gesture.view))
    }
}

Beta

This is still in Beta and may change quickly.

For a session discussing this please direct to: WWDC 24 What's new in SwiftUI

Upvotes: 1

user832
user832

Reputation: 846

Below solution adds a pin at the point where user long presses on the Map.

Add below method in MapViewCoordinator

@objc func addPinBasedOnGesture(_ gestureRecognizer:UIGestureRecognizer) {
    let touchPoint = gestureRecognizer.location(in: gestureRecognizer.view)
    let newCoordinates = (gestureRecognizer.view as? MKMapView)?.convert(touchPoint, toCoordinateFrom: gestureRecognizer.view)
    let annotation = PinAnnotation()
    guard let _newCoordinates = newCoordinates else { return }
    annotation.coordinate = _newCoordinates
    mapViewController.annotations.append(annotation)
}

and longPress gesture code in func makeUIView(context: Context) -> MKMapView {}

    func makeUIView(context: Context) -> MKMapView {
    let mapView = MKMapView()
    mapView.delegate = context.coordinator
    let longPressed = UILongPressGestureRecognizer(target: context.coordinator,
                                                   action: #selector(context.coordinator.addPinBasedOnGesture(_:)))
    mapView.addGestureRecognizer(longPressed)
    return mapView
}

Upvotes: 4

Asperi
Asperi

Reputation: 258345

Find below modified parts of provided code to get required behaviour:

struct SUMapView: UIViewRepresentable {

    // ... other your code here

    func makeUIView(context: Context) -> MKMapView {
        let mapView = MKMapView()
        mapView.delegate = context.coordinator

        let longPressed = UILongPressGestureRecognizer(target: 
              context.coordinator, action: #selector(addPin(gesture:)))
        mapView.addGestureRecognizer(longPressed)

        return mapView
    }

    // ... other your code here
}

class MapViewCoordinator: NSObject, MKMapViewDelegate {

    // ... other your code here

    @objc func addPin(gesture: UILongPressGestureRecognizer) {
        // do whatever needed here
    }

    // ... other your code here

}

Upvotes: 0

Related Questions