MapProxy conversion from screen to coords is wrong on macOS

Try the following code on macOS, and you'll see the marker is added in the wrong place, as the conversion from screen coordinates to map coordinates doesn't work correctly.

The screenCoord value is correct, but reader.convert(screenCoord, from: .local) offsets the resulting coordinate by the height of the content above the map, despite the .local parameter.

struct TestMapView: View {
@State var placeAPin = false
@State var pinLocation :CLLocationCoordinate2D? = nil
@State private var cameraProsition: MapCameraPosition = .camera(
MapCamera(
centerCoordinate: .denver,
distance: 3729,
heading: 92,
pitch: 70
)
)
var body: some View {
VStack {
Text("This is a bug demo.")
Text("If there are other views above the map, the MapProxy doesn't convert the coordinates correctly.")
MapReader { reader in
Map(
position: $cameraProsition,
interactionModes: .all
)
{
if let pl = pinLocation {
Marker("(\(pl.latitude), \(pl.longitude))", coordinate: pl)
}
}
.onTapGesture(perform: { screenCoord in
pinLocation = reader.convert(screenCoord, from: .local)
placeAPin = false
if let pinLocation {
print("tap: screen \(screenCoord), location \(pinLocation)")
}
})
.mapControls{
MapCompass()
MapScaleView()
MapPitchToggle()
}
.mapStyle(.standard(elevation: .automatic))
}
}
}
}
extension CLLocationCoordinate2D {
static var denver = CLLocationCoordinate2D(latitude: 39.742043, longitude: -104.991531)
}

(FB13135770)

What is screenCoord? Where does it come from?

It's a point. The closure argument for .onTapGesture(perform:).

(Oops, this was supposed to be a comment; I don't see how to delete it.)

Can you try using a custom coordinate space like this and see if it works. I can't test it because I'm on Ventura.

Map {
...
}
.onTapGesture(perform: { screenCoord in
pinLocation = reader.convert(screenCoord, from: .named("map"))
...
})
.coordinateSpace(.named("map"))

Just ran into this. But... I don't have any views above the map. In my case the Y coordinate seems to be off by 25 points. If I add 25.0 to the Y coordinate of the reported position before conversion I get what I believe to be the correct value. Or certainly something much, much closer to the correct value.

macOS 14.3.1.

I have tried:

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)
}
}

by user nightwill from this question at StackOverflow:

https://stackoverflow.com/questions/77354568/swiftui-mapkit-drag-annotation

and I noticed that on macOS 14.3.1 and Xcode 15 the touchable rect is under the marker most of the time, except when the only window in the app is the map and the app is in full screen.

You can check the touchable rect by implementing this in the code from nightwill (I implemented it after onMapCameraChange in the code above):

.overlay {
if let markerData {
Rectangle()
.stroke(Color.red, lineWidth: 2)
.frame(width: rectWidth, height: rectWidth)
.position(x: markerData.screenPoint.x, y: markerData.screenPoint.y)
}
}
MapProxy conversion from screen to coords is wrong on macOS
 
 
Q