Search code examples
swiftuiruntime-errormapkit

SwiftUI Map causes "modifying state during view update"


I'd like to implement a basic Map view that will center on the users location when they tap a button, similar to the Apple Maps app. I tried the following, but whenever I tap the button, [SwiftUI] Modifying state during view update, this will cause undefined behavior. is printed in the console. It seems to me that updating the tracking state variable is causing the error. However, I'm not sure how else the state variable is meant to be used. The app does behave as intended despite printing the error. Does anyone have any experience with this or know what might be wrong?

struct ContentView: View {
    @State var region: MKCoordinateRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 47.3769, longitude: 8.5417), latitudinalMeters: 2000, longitudinalMeters: 2000)
    @State var tracking = MapUserTrackingMode.follow
    
    var body: some View {
        ZStack {
            Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true, userTrackingMode: $tracking)
                .ignoresSafeArea()
                .task {
                    let locationManager = CLLocationManager()
                    locationManager.requestWhenInUseAuthorization();
                }
            Button {
                tracking = .follow
            } label: {
                Image(systemName: tracking == .follow ? "location.fill" : "location")
                    .padding()
            }
            .background(.white)
        }
    }
}

Solution

  • Edit 27/7/2023:

    • Bug still occurs in Xcode 15.0 beta 5 (15A5209g) with iOS 17 beta
    • MapUserTrackingMode deprecated in iOS 17
    • Update on FB9990674
      • Recent similar reports: More than 10.
      • Resolution: potential fix identified for future OS update.
    • Can be worked around with the new MapCameraPosition API (may change during beta period), e.g.
    struct MapTestView3: View {
       
        //@State var region: MKCoordinateRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 47.3769, longitude: 8.5417), latitudinalMeters: 2000, longitudinalMeters: 2000)
        //@State var tracking = MapUserTrackingMode.follow
        
        static let fallback = MapCameraPosition.region(MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 47.3769, longitude: 8.5417), latitudinalMeters: 2000, longitudinalMeters: 2000))
        @State var position = MapCameraPosition.userLocation(fallback: Self.fallback)
        
        var body: some View {
            //let _ = Self._printChanges()
            VStack {
                Map(position: $position)
                //Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true, userTrackingMode: $tracking)
                    .ignoresSafeArea()
                    .onAppear {
                        let locationManager = CLLocationManager()
                        locationManager.requestWhenInUseAuthorization();
                    }
                Button {
                    //tracking = .follow
                    position = MapCameraPosition.userLocation(fallback: Self.fallback)
                } label: {
                    
                    Image(systemName: position.followsUserLocation ? "location.fill" : "location")
                        .padding()
                }
                .background(.white)
            }
        }
    }
    

    Original:

    Seems to me it's a bug in Map (as of Xcode Version 13.3.1 (13E500a) and iPhone 13 Simulator). If you switch to the Breakpoints side-bar and click the + and add an All Runtime Issues breakpoint, if you debug and click the button you'll hit the breakpoint and see this:

    enter image description here

    This trace shows that when the button is tapped to change the tracking state, SwiftUI updates MKMapView with the new state by calling _setUserTrackingMode (line 13) but a side effect of this is a callback to mapLayerDidChangeVisibleRegion (line 9) and it tries to set the value of a Binding (line 6), most likely the coordinateRegion. It shouldn't be setting a Binding while it is updating the MKMapView from the State, which is what results in the warning. We should all report the bug - I submitted it as FB9990674 under Developer Tools - SwiftUI, feel free to reference my number.