Select to view content in your preferred language

Zoom to location

2241
6
Jump to solution
03-30-2023 06:33 AM
GBreen
by
Occasional Contributor

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()
    }
}
0 Kudos
1 Solution

Accepted Solutions
rolson_esri
Esri Alum

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.

View solution in original post

6 Replies
DuanePfeiffer
Regular Contributor

I accomplish this by setting the locationDisplay.autoPanMode.  Not sure if that is your desired outcome or not.

        appData.locationDisplay.autoPanMode = .recenter

0 Kudos
GBreen
by
Occasional Contributor

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

 

0 Kudos
GBreen
by
Occasional Contributor

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.

0 Kudos
rolson_esri
Esri Alum

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.

GBreen
by
Occasional Contributor

Oh Awesome! I'm already using the MapViewReader for and identify operation so that will be super simple to implement. Thanks!

Ting
by Esri Contributor
Esri Contributor

With the 200.1 release, we have a Change viewpoint sample that shows this API. 🙂

0 Kudos