I'm really liking the new SwiftUI implementation but I'm having trouble with one thing. I have a button to zoom to the current location. In order to do that I set a viewpoint and it works the first time. But if I move and try to tap it again it doesn't zoom. The workaround is I watch the viewpoint for changes and set it back to nil immediately. Is this the best way to implement zooming or is there some other way to do it? I can't find anything in the docs for it. Here's the example code:
struct MapZoomTestView: View {
@StateObject private var map: Map = {
let map = Map(basemapStyle: .arcGISTopographic)
map.initialViewpoint = Viewpoint(latitude: 34.02700, longitude: -118.80500, scale: 72_000)
return map
}()
@State var viewpoint: Viewpoint? = nil
let locationDisplay = LocationDisplay(dataSource: SystemLocationDataSource())
init() {
ArcGISEnvironment.apiKey = APIKey("")
}
var body: some View {
MapView(map: map, viewpoint: viewpoint)
.locationDisplay(locationDisplay)
.task {
let locationManager = CLLocationManager()
if locationManager.authorizationStatus == .notDetermined {
locationManager.requestWhenInUseAuthorization()
}
do {
try await locationDisplay.dataSource.start()
} catch {
print("Current Location Cannot Be Shown")
}
}
.overlay(alignment: .bottomLeading) {
Button {
guard let location = locationDisplay.location else {
return
}
viewpoint = Viewpoint(center: location.position, scale: 10_000)
} label: {
Image(systemName: "paperplane.fill")
}
.frame(width: 50, height: 50, alignment: .center)
.background(.regularMaterial)
.cornerRadius(25.0)
.padding([.leading, .bottom])
}
.onChange(of: viewpoint) { newValue in
guard newValue != nil else {
return
}
viewpoint = nil
}
.ignoresSafeArea()
}
}
Solved! Go to Solution.
What you have works with the beta release. As you have noticed, this isn't ideal. Final (200.1) should be released soon and there will be a much better way to achieve what you are looking for via a MapViewReader and the associated proxy:
var body: some View {
MapViewReader { proxy in
MapView(map: map, viewpoint: viewpoint)
.locationDisplay(locationDisplay)
.task {
let locationManager = CLLocationManager()
if locationManager.authorizationStatus == .notDetermined {
locationManager.requestWhenInUseAuthorization()
}
do {
try await locationDisplay.dataSource.start()
} catch {
print("Current Location Cannot Be Shown")
}
}
.overlay(alignment: .bottomLeading) {
Button {
guard let location = locationDisplay.location else {
return
}
Task {
await proxy.setViewpointCenter(location.position, scale: 10_000)
}
} label: {
Image(systemName: "paperplane.fill")
}
.frame(width: 50, height: 50, alignment: .center)
.background(.regularMaterial)
.cornerRadius(25.0)
.padding([.leading, .bottom])
}
.ignoresSafeArea()
}
}
Note: the call above that sets the viewpoint (`proxy.setViewpointCenter(:scale:)`) will actually animate to the new viewpoint. You can tell that because it's an async call. There will also be an un-animated setViewpoint function on the proxy once we release 200.1.
I accomplish this by setting the locationDisplay.autoPanMode. Not sure if that is your desired outcome or not.
appData.locationDisplay.autoPanMode = .recenter
Yeah I thought about that too but then the map follows the user where I just want them to be able to decide when to zoom, not auto pan. I could do something like below but that seems just as hacky
locationDisplay.autoPanMode = .recenter
locationDisplay.autoPanMode = .off
Also I would like a solution that can be used to zoom to other geometries as well, not just the current location if that makes sense.
What you have works with the beta release. As you have noticed, this isn't ideal. Final (200.1) should be released soon and there will be a much better way to achieve what you are looking for via a MapViewReader and the associated proxy:
var body: some View {
MapViewReader { proxy in
MapView(map: map, viewpoint: viewpoint)
.locationDisplay(locationDisplay)
.task {
let locationManager = CLLocationManager()
if locationManager.authorizationStatus == .notDetermined {
locationManager.requestWhenInUseAuthorization()
}
do {
try await locationDisplay.dataSource.start()
} catch {
print("Current Location Cannot Be Shown")
}
}
.overlay(alignment: .bottomLeading) {
Button {
guard let location = locationDisplay.location else {
return
}
Task {
await proxy.setViewpointCenter(location.position, scale: 10_000)
}
} label: {
Image(systemName: "paperplane.fill")
}
.frame(width: 50, height: 50, alignment: .center)
.background(.regularMaterial)
.cornerRadius(25.0)
.padding([.leading, .bottom])
}
.ignoresSafeArea()
}
}
Note: the call above that sets the viewpoint (`proxy.setViewpointCenter(:scale:)`) will actually animate to the new viewpoint. You can tell that because it's an async call. There will also be an un-animated setViewpoint function on the proxy once we release 200.1.
With the 200.1 release, we have a Change viewpoint sample that shows this API. 🙂