Reinhard Männer
Reinhard Männer

Reputation: 15247

SwiftUI Map: How to convert screen coordinates to map coordinates?

Setup:

I am converting an app to SwiftUI. This app had an MKMapView with annotations that could be dragged on the map.
I was able to write a first version of the SwiftUI app with a draggable image.
It uses a View that is displayed as annotation on the map:

struct AnnotationView: View {
    var buyLocationNameBeforeDragging: String
    @State private var isDragging = false
    @GestureState var dragAmount = CGSize.zero
    var region: MKCoordinateRegion
    
    func coordinateFrom(region: MKCoordinateRegion, dragAmount: CGSize) -> CLLocationCoordinate2D {
        let coordinate = CLLocationCoordinate2D() // How to compute this?
        return coordinate
    }
    
    var drag: some Gesture {
        DragGesture()
            .onChanged { _ in
                self.isDragging = true
            }
            .onEnded { _ in
                self.isDragging = false
                let coreDataManager = testOrPreview ? CoreDataManager.test : CoreDataManager.shared
                let newCoordinate = coordinateFrom(region: region, dragAmount: dragAmount)
                // Move the dragged place to the new coordinate
                // …
            }
            .updating($dragAmount) { value, state, transaction in
                state = value.translation
            }
    }
    
    var body: some View {
        Image("PinRed")
            .offset(dragAmount)
            .defersSystemGestures(on: .all)
            .onTapGesture {
                print("tapped")
            }
            .gesture(drag)
    }
}  

It is possible to tap the annotation pin, and - after tapping longer - to drag the pin to a new location.

Problem:

To update my model, I need the endpoint coordinates of the drag on the map. These should be computed in func coordinateFrom (see above), but I have no idea how to do this.
MKMapView has e.g. a function convert(_:toCoordinateFrom:), but SwiftUI's Map has nothing like this.

Question:

How to do the conversion?

Upvotes: 2

Views: 1055

Answers (1)

Reinhard Männer
Reinhard Männer

Reputation: 15247

Problem solved.
I 1st converted var drag: some Gesture to a function:

func drag(geo: GeometryProxy) -> some Gesture {
    DragGesture(coordinateSpace: .named("screen"))
        .onChanged { value in
            self.isDragging = true
        }
        .onEnded { _ in
            self.isDragging = false
            let coreDataManager = testOrPreview ? CoreDataManager.test : CoreDataManager.shared
            let newCoordinate = coordinateFrom(region: region, geo: geo, dragAmount: dragAmount)
            // Move the dragged place to the new coordinate
            // …
        }
        .updating($dragAmount) { value, state, transaction in
            state = value.translation
        }
}

where geo is supplied by the body of the AnnotationView, and .coordinateSpace(name: "screen") has been applied to the view displaying the map:

var body: some View {
    GeometryReader { geo in
        Image("PinRed")
            .offset(dragAmount)
            .defersSystemGestures(on: .all)
            .onTapGesture {
                print("Global center: \(geo.frame(in: .global).midX) x \(geo.frame(in: .global).midY)")
            }
            .gesture(drag(geo: geo))
    }
    .frame(width: 40, height: 30)
}

Next, I converted the map coordinates from screen coordinates to map coordinates using the old MKMapView as:

func coordinateFrom(region: MKCoordinateRegion, geo: GeometryProxy, dragAmount: CGSize) -> CLLocationCoordinate2D {
    let mkMapView = MKMapView(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.size.width, height: UIScreen.main.bounds.size.height))
    mkMapView.region = region
    let frame = geo.frame(in: .global)
    let dragEndPoint = CGPoint(x: frame.origin.x + dragAmount.width, y: frame.origin.y + dragAmount.height)
    let dragEndCFoordinate = mkMapView.convert(dragEndPoint, toCoordinateFrom: mkMapView)
    return dragEndCFoordinate
}

Here, I assume the MKMapView and SwiftUI Map use the same coordinate transforms. In my case, I don't see any differences.

Upvotes: 3

Related Questions