Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI: Publishing changes from within view updates is not allowed, this will cause undefined behavior (when using `ViewModel` approach)

I've read a number of questions about this error that relates to dismissing sheets, but none dealing with SwiftUI's Map. The following code generates this error. Nothing is being updated in the view model. I'm simply passing a binding to a region into the Map initializer. Using a local state variable for region works with no error. I'm running Xcode 14.0. If I remove the @Published property wrapper then the error goes away. So I'm confused as to how the view model should notify the view that the region has changed, perhaps due to location updates.

import SwiftUI
import MapKit

class MM : ObservableObject {
    @Published var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
}

struct SimpleMap: View {
    @ObservedObject var mm = MM()
    @State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))

    var body: some View {
        //Error
        Map(coordinateRegion: $mm.region)
        
        //No Error
        //Map(coordinateRegion: $region)
    }
}

like image 358
JohnH Avatar asked Sep 13 '25 14:09

JohnH


2 Answers

Did you try updating your code like this?

class MM : ObservableObject {
    @Published var _region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 51.507222, longitude: -0.1275), span: MKCoordinateSpan(latitudeDelta: 0.5, longitudeDelta: 0.5))
    var region: MKCoordinateRegion {
        get { _region }
        set {
            DispatchQueue.main.async {
                self._region = newValue
            }
        }
    }
}

struct SimpleMap: View {
    @StateObject var mm = MM()
    
    var body: some View {
        //Error
        Map(coordinateRegion: $mm.region)
    }
}

Making changes to the Publishers or State variables while the view is being updated, causes the issue. To prevent the issue, we should update the Publisher values asynchronously in the main queue. Let me know if this doesn't work.

like image 110
Mayank Bhaisora Avatar answered Sep 15 '25 08:09

Mayank Bhaisora


Why does this happen?

No matter what you do it is just a working around solution because this warnings is a reported issue of SwiftUI.Map prior to iOS 17.0.

Publishing changes from within view updates is not allowed, this will cause undefined behavior.

Binding of region:

Whenever the map received a new center, an example from (0, 0) to (10, 10), the map internally sets the region to a sequence of values as below to animate the change [(1, 1), (2, 2), ..., (10, 10)].

You can try replacing the Binding with a custom to get a closer look.

Map(
    coordinateRegion: Binding(get: {
        print("Get region: \(region)")
        region
    }, set: { newValue in
        print("Set region: \(newValue)")
        region = newValue
    })
)

MapAnnotation:

If the map was updated, the visible map annotations will get updated which is totally fine. But MapAnnoation somehow modifies the internal states of the map causing the warning.

Solutions

iOS 17.0 and above

Please use the new API, this is the documentation.

iOS 16.0 and lower

final class MapViewModel: ObservableObject {
    var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(latitude: 48.876192, longitude: 2.333455),
        latitudinalMeters: 3000,
        longitudinalMeters: 3000
    )
}

struct MapView: View {
    @StateObject var viewModel = MapViewModel()
    
    var body: some View {
        Map(
            coordinateRegion: $viewModel.region,
            annotationItems: viewModel.annotationItems,
            annotationContent: annotation(for:)
        )
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .ignoresSafeArea()
    }
}
like image 20
tphduy Avatar answered Sep 15 '25 07:09

tphduy