I've seen similar questions on this but none that are up to date or work successfully, I've followed Apples Developer docs, various videos, and guides on how to do this but it still throws these warnings
2023-01-11 11:33:16.044044-0500 Walkman[50986:4824621] [SwiftUI] Publishing changes from within view updates is not allowed, this will cause undefined behavior.
It feels like I've tried everything from onChange() to @StateObject instead of @ObservedObject... It's those few lines because the warnings go away if the Map gets commented out.
The code that's causing the error is the
struct OrderView: View {
@ObservedObject var cartManager: CartManager
// @ObservedObject var locationManager: LocationManager
@EnvironmentObject var locationManager: LocationManager
@EnvironmentObject var viewModel: AppViewModel
@State private var tracking:MapUserTrackingMode = .follow
@State private var driversIn: Bool = false
var body: some View {
NavigationView {
// Text("Testing")
Map(
coordinateRegion: $locationManager.region,
interactionModes: MapInteractionModes.all,
showsUserLocation: true,
userTrackingMode: $tracking,
annotationItems: locationManager.MapLocations,
annotationContent: { item in
MapMarker(coordinate: item.coordinate, tint: .red)
// MapAnnotation(coordinate: item.location) {
// Rectangle()
// .stroke(Color.purple, lineWidth: 3)
// .frame(width: 10, height: 10)
// }
}
)
.navigationTitle("Orders")
.onAppear {
locationManager.getNearbyDrivers(query: GeoQuery(distance: 400, geo: locationManager.createGeo()))
}
.ignoresSafeArea()
// .onReceive(locationManager.$drivers, perform: { drivers in
//// print("DEBUG: DRIVERS _ \(drivers)")
// for driver in drivers {
// print("DEBUG: DRIVER TEST _ \(driver)")
// locationManager.MapLocations.append(MapLocation(id: driver.id, name: driver.name, latitude: driver.location.coordinates[0], longitude: driver.location.coordinates[1]))
// }
// })
}
}
}
Here is my LocationManager
final class LocationManager: NSObject, CLLocationManagerDelegate, ObservableObject {
@Published var region = MKCoordinateRegion(center: MapDetails.startingLocation, span: MapDetails.defaultSpan)
@Published var authorizationStatus: CLAuthorizationStatus?
@Published var location: CLLocation?
@Published var MapLocations = [MapLocation]()
@Published var drivers = [Driver]()
var locationManager = CLLocationManager()
override init() {
super.init()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
}
func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
switch manager.authorizationStatus {
case .authorizedWhenInUse:
authorizationStatus = .authorizedWhenInUse
manager.startUpdatingLocation()
break
case .restricted, .denied:
authorizationStatus = .restricted
manager.stopUpdatingLocation()
break
case .notDetermined:
authorizationStatus = .notDetermined
manager.requestWhenInUseAuthorization()
break
default:
break
}
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.last else { return }
DispatchQueue.main.async {
self.location = location
// self.MapLocations.append(MapLocation(name: "1", latitude: location.coordinate.latitude, longitude: location.coordinate.longitude))
// MARK - API for location calls, send when users location updates
print("DEBUG: Location: \(self.location?.coordinate.longitude ?? 0.00)")
}
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("DEBUG: \(error.localizedDescription)")
}
func createGeo() -> GeoJSON {
return GeoJSON(coordinates: [locationManager.location?.coordinate.latitude ?? 0.00, locationManager.location?.coordinate.longitude ?? 0.00])
}
func getNearbyDrivers(query: GeoQuery) {
guard let url = URL(string: "http://localhost:8000/drivers-nearby") else { fatalError("Missing URL") }
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let payload: [String: Any] = [
"distance": query.distance,
"geo": ["type": "Point", "coordinates": query.geo.coordinates]
]
let data = try! JSONSerialization.data(withJSONObject: payload, options: [])
request.httpBody = data
let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
if let error = error {
print("Request error: ", error)
return
}
guard let response = response as? HTTPURLResponse else { return }
if response.statusCode == 200 {
guard let data = data else { return }
DispatchQueue.main.async {
do {
let drivers = try JSONDecoder().decode([Driver].self, from: data)
print("DEBUG: Drivers: \(drivers)")
self.drivers = drivers
for driver in self.drivers {
let location = MapLocation(id: driver.id, name: driver.name, latitude: driver.location.coordinates[0], longitude: driver.location.coordinates[1])
var driverIsVisible: Bool {
return self.MapLocations.contains(where: { annotation in
guard var driverAnno = annotation as? MapLocation else { return false }
print("DEBUG: cast driverAnno | driverAnno \(driverAnno.id) | driver \(driver.id)")
if driverAnno.id == driver.id {
print("DEBUG: Handle update driver position")
driverAnno.latitude = driver.location.coordinates[0]
driverAnno.longitude = driver.location.coordinates[1]
return true
}
return false
})
}
if !driverIsVisible {
print("DEBUG: Inserting map annotation \(location.id)")
self.MapLocations.append(location)
}
}
} catch let error {
print("Error decoding: ", error)
}
}
}
}
dataTask.resume()
}
//// private func geocode() {
//// guard let location = self.location else { return }
//// geocoder.reverseGeocodeLocation(location) { (places, error) in
//// if error == nil {
//// self.placemark = places?[0]
//// } else {
//// self.placemark = nil
//// }
//// }
//// }
}
I had a similar issue, and all the web answers seem to suggest this is an Apple bug which might be true but there is a workaround.
Patrick above already hinted at the culprit, it has to do with
@Published
variable, but removing it is not the correct solution for you. The problem is becauseregion
is a@Published
variable and it is also used as aBinding
variable (i.e.$locationManager.region
in your code). There is nothing wrong to have code pattern like this, but I am guessing Map framework doesn't like it (thus the apple bug).workaround is you need to have an intermediate step in the SwiftUI view that will act on the changes in the
@Published
variable and bind to the UI, so you don't bind the published variable directly to thecoordinateRegion
example: