swiftuiannotationsmapkitdrag

SwiftUI MapKit Drag Annotation


Does anyone has experience or an idea how to drag & drop Annotations and Markers with the new MapKit Features released this year?

import MapKit
import SwiftUI

struct SwiftUIView: View {

    @State private var position: MapCameraPosition = .automatic

    var body: some View {
        Map(position: $position) {
            Marker("name", coordinate: CLLocationCoordinate2D(latitude: 48.186915, longitude: 11.164182))
        }
    }
}

Solution

  • import MapKit
    import SwiftUI
    
    private let rectWidth: Double = 80
    
    private struct MarkerData {
        let coordinate: CLLocationCoordinate2D
        let screenPoint: CGPoint
    
        var touchableRect: CGRect {
            .init(x: screenPoint.x - rectWidth / 2, y: screenPoint.y - rectWidth / 2, width: rectWidth, height: rectWidth)
        }
    }
    
    struct ContentView: View {
    
        @State private var cameraPosition: MapCameraPosition = .automatic
        @State private var modes: MapInteractionModes = [.all]
        @State private var isMarkerDragging = false
        @State private var markerData: MarkerData?
    
        var body: some View {
            GeometryReader { geometryProxy in
                MapReader { mapProxy in
                    Map(position: $cameraPosition, interactionModes: modes) {
                        if let markerData {
                            Marker("Start", coordinate: markerData.coordinate)
                        }
                    }
                    .onTapGesture { screenCoordinate in
                        self.markerData = mapProxy.markerData(screenCoordinate: screenCoordinate, geometryProxy: geometryProxy)
                    }
                    .highPriorityGesture(DragGesture(minimumDistance: 1)
                        .onChanged { drag in
                            guard let markerData else { return }
                            if isMarkerDragging {
    
                            } else if markerData.touchableRect.contains(drag.startLocation) {
                                isMarkerDragging = true
                                setMapInteraction(enabled: false)
                            } else {
                                return
                            }
    
                            self.markerData = mapProxy.markerData(screenCoordinate: drag.location, geometryProxy: geometryProxy)
                        }
                        .onEnded { drag in
                            setMapInteraction(enabled: true)
                            isMarkerDragging = false
                        }
                    )
                    .onMapCameraChange {
                        guard let markerData else { return }
                        self.markerData = mapProxy.markerData(coordinate: markerData.coordinate, geometryProxy: geometryProxy)
                    }
                }
            }
        }
    
        private func setMapInteraction(enabled: Bool) {
            if enabled {
                modes = .all
            } else {
                modes = []
            }
        }
    }
    
    private extension MapProxy {
    
        func markerData(screenCoordinate: CGPoint, geometryProxy: GeometryProxy) -> MarkerData? {
            guard let coordinate = convert(screenCoordinate, from: .local) else { return nil }
            return .init(coordinate: coordinate, screenPoint: screenCoordinate)
        }
    
        func markerData(coordinate: CLLocationCoordinate2D, geometryProxy: GeometryProxy) -> MarkerData? {
            guard let point = convert(coordinate, to: .local) else { return nil }
            return .init(coordinate: coordinate, screenPoint: point)
        }
    }
    

    In this example, a marker is added by tapping to the screen and can be moved. If you need a preset marker you can add

    .onAppear {
          self.markerData = mapProxy.markerData(coordinate: #YOUR_COORDINATE#, geometryProxy: geometryProxy)
    }
    

    Tested on macOS and iPadOS