Using SwiftUI MapReader to position Views over map

3 weeks ago 40
ARTICLE AD BOX

I'm trying to use SwiftUI to make a ZStack containing a Map (with a MapKit map view), and a Circle view that is placed on top of it. The Circle's pixel position should be to the pixel position on the map corresponding to a certain lat/lng coordinate.

I'm not using MapAnnotation etc that are meant to be part of the Map itself, but instead separate Views on top of the Map view. (Because in the real application, the view's position is not directly the x/y of a lat/lng coordinate, but is calculated from that.)

I currently have code like the following:

import SwiftUI import MapKit import CoreLocation struct ContentView: View { @State private var mapCenterCoordinate = CLLocationCoordinate2D( latitude: 37.3342284, longitude: -122.0106683 ) @State private var shownCoordinate = CLLocationCoordinate2D( latitude: 37.3347206, longitude: -122.0059942 ) @State private var heading: Double = 0 @State private var xyPosition: CGPoint? private var mapCameraPosition: MapCameraPosition { return .camera( MapCamera( centerCoordinate: mapCenterCoordinate, distance: 3000, heading: heading, pitch: 0 ) ) } var body: some View { VStack { MapReader { proxy in ZStack { Map(position: .constant(mapCameraPosition)) .onMapCameraChange() { xyPosition = proxy.convert(shownCoordinate, to: .local) print("xyPosition changed to \(xyPosition)") } if xyPosition != nil { Circle() .fill(.red) .frame(width: 12, height: 12) .position(xyPosition!) } } } Slider(value: $heading, in: 0...360, step: 1) } } }

The ZStack is wrapped in a MapReader, which should give a proxy: MapProxy object to the view closure, from which coordinates can be converted between the map (as it will be displayed) and the view's pixel space.

The map is centered around mapCenterCoordinate, with a heading set by @State heading, which can be adjusted by the slider. So moving the slider rotates the map, it causes the Map to be reset with a new position argument.

I'm using the MapProxy in onMapCameraChange(), to set @State xyPosition to the x,y pixel positions that correspond to shownCoordinate, with the map's current position/heading.


This words correctly when I move/rotate the map by swiping on the map view itself. The red circle correctly gets placed in the correct position.

But when I move the slider to rotate the map (and also center it back to mapCenterCoordinate), it does not work: onMapCameraChange() gets called, but xyPosition always gets set to nil. Even when shownCoordinate is in bounds of the map.

Is there a way to make this work?

In the real application, there would be no user interaction on the map view itself.

I've also tried calling proxy.convert() directly in the view closure of ZStack (after Map()), not using onMapCameraChange(), but this leads to "AttributeGraph: cycle detected" errors.

Alternately, is there a way to directly calculate the x/y from lat/lng from the MapCamera, without using MapReader/MapProxy?

Read Entire Article