3

I have a project that uses the new SwiftUI Map in iOS 14

I want to be able to update the location of the Map centre dynamically.

When you tap the Zoom button and then the Location Button the Map works fine and re-centers to London.

However if you just tap the location button it re-centers to London but throws a warning about the ViewState.

I am at a loss for what is causing this issue and how to fix it.

struct ContentView: View {
    @State private var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(
            latitude: 25.7617,
            longitude: 80.1918
        ),
        span: MKCoordinateSpan(
            latitudeDelta: 10,
            longitudeDelta: 10
        )
    )

    var body: some View {
        VStack {
            Map(coordinateRegion: $region)
            Button("zoom") {
                withAnimation {
                    region.span = MKCoordinateSpan(
                        latitudeDelta: 100,
                        longitudeDelta: 100
                    )
                }
            }
            Button(action: {
                withAnimation {
                    region = MKCoordinateRegion(center: CLLocationCoordinate2D(
                                                    latitude: 51.507222,
                                                    longitude: -0.1275),
                                                span: MKCoordinateSpan(
                                                    latitudeDelta: 0.5,
                                                    longitudeDelta: 0.5))
                }
            }) {
                Image(systemName: "location.fill")
                    .frame(width: 44, height: 44, alignment: .center)
                    .foregroundColor(.black)
                    .background(Color(.white))
            }
            .buttonStyle(PlainButtonStyle())
            .clipShape(Circle())
            .shadow(color: .black.opacity(0.5), radius: 1.0, x: 0.0, y: 2.0)
        }
    }
}
Abizern
  • 146,289
  • 39
  • 203
  • 257

1 Answers1

3

This is one of those errors that will drive you completely nuts until you accept that it means EXACTLY what it says: runtime: SwiftUI: Modifying state during view update, this will cause undefined behavior. The button is in the view, and when you press it, you are updating the view AND trying to modify it. The solution is to have this sort of modified data come from outside of your view struct.

I made a class to hold the map coordinates:

import Foundation
import MapKit

class Coordinates: ObservableObject {
    public static let shared = Coordinates()
    @Published var region: MKCoordinateRegion
    
    init() {
        self.region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(
            latitude: 25.7617,
            longitude: 80.1918
        ),
        span: MKCoordinateSpan(
            latitudeDelta: 10,
            longitudeDelta: 10
        )
    )
    }
}

I then modified ContentView to accept the data:

struct ContentView: View {
    @ObservedObject var coordinates = Coordinates.shared
    
    var body: some View {
        VStack {
            Map(coordinateRegion: $coordinates.region)
            Button("zoom") {
                withAnimation {
                    coordinates.region.span = MKCoordinateSpan(
                        latitudeDelta: 100,
                        longitudeDelta: 100
                    )
                }
            }
            Button(action: {
                withAnimation {
                    coordinates.region = MKCoordinateRegion(center: CLLocationCoordinate2D(
                                                    latitude: 51.507222,
                                                    longitude: -0.1275),
                                                span: MKCoordinateSpan(
                                                    latitudeDelta: 0.5,
                                                    longitudeDelta: 0.5))
                }
            }) {
                Image(systemName: "location.fill")
                    .frame(width: 44, height: 44, alignment: .center)
                    .foregroundColor(.black)
                    .background(Color(.white))
            }
            .buttonStyle(PlainButtonStyle())
            .clipShape(Circle())
            .shadow(color: .black.opacity(0.5), radius: 1.0, x: 0.0, y: 2.0)
        }
    }
}

You don't have to do it exactly this way, but you need to keep in mind the MVVM principals upon which SwiftUI is built and separate your source of truth from your view. I would run through Apple's SwiftUI tutorials if you haven't already, and then Paul Hegarty's CS193p class to really get a feel how it works.

Yrb
  • 8,103
  • 2
  • 14
  • 44