Reputation: 91
I am using the code below to do the following.
If I am in the listView, the list gets properly updated every 5 seconds with the new item. No error message. If I am in the mapView, the map also gets updated (a new marker every 5 seconds), but I get error "[SwiftUI] Publishing changes from within view updates is not allowed, this will cause undefined behavior." Since list and map both display the same model data, I wonder why map complains and list does not. The actual model update is on the main actor, so why is it complaining.
Any idea?
//Model
struct TestApp1Model {
struct TestItem: Identifiable {
var id = UUID()
var name: String
var latitude: Double
var longitude: Double
}
var items = [TestItem]()
}
// ViewModel
class TestApp1ViewModel: ObservableObject {
@Published private var model = TestApp1Model()
private var timer:Timer?
init() {
timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { _ in
Task { @MainActor in
self.addItem()
}
}
}
var items:[TestApp1Model.TestItem] {
model.items
}
@MainActor func addItem () {
let name = "Item " + model.items.count.description
let latitude = Double.random(in: 45...55)
let longitude = Double.random(in: 5...11)
model.items.append(TestApp1Model.TestItem(name: name, latitude: latitude, longitude: longitude))
}
}
// View
struct TestApp1View: View {
@StateObject var testVM = TestApp1ViewModel()
@State var region:MKCoordinateRegion
init() {
self.region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 50, longitude: 8), span: MKCoordinateSpan(latitudeDelta: 10, longitudeDelta: 6))
}
var body: some View {
TabView {
listView
.tabItem {
Image(systemName: "list.bullet")
Text("List")
}
.backgroundStyle(Color.white)
mapView
.tabItem {
Image(systemName: "map")
Text("Map")
}
.backgroundStyle(Color.white)
}
}
var listView: some View {
VStack {
List (testVM.items) { item in
HStack {
Text(item.name)
Text(item.latitude.description)
Text(item.longitude.description)
}
}
}
}
var mapView: some View {
Map(coordinateRegion: $region, interactionModes: .all, showsUserLocation: true,annotationItems: testVM.items) {item in
MapAnnotation(coordinate: CLLocationCoordinate2D(latitude: item.latitude, longitude: item.longitude)) {
Image(systemName: "plus")
.foregroundColor(.red)
}
}
.ignoresSafeArea()
}
}
Upvotes: 1
Views: 2811
Reputation: 816
Add a manual binding with its setter working in the main thread.
This code was producing the same warning:
struct AreaMap: View {
@Binding var region: MKCoordinateRegion
var body: some View {
Map(coordinateRegion: $region)
}
}
Where region
was passed in the initializer of AreaMap
from its parent view.
And this resolved the issue for me:
struct AreaMap: View {
@Binding var region: MKCoordinateRegion
var body: some View {
let binding = Binding(
get: { self.region },
set: { newValue in
DispatchQueue.main.async {
self.region = newValue
}
}
)
return Map(coordinateRegion: binding)
}
}
Upvotes: 0
Reputation: 656
I'm having the same issue with Map(). It started appearing with Xcode 14. Other people see this run-time warning in other scenarios. For now, I assume it's a bug that will be fixed in future Xcode updates.
See: https://developer.apple.com/forums/thread/711899
Upvotes: 0
Reputation: 251
I've come across similar issues with SwiftUI's Map to the point where I'm convinced it's a bug. In the cases I've encountered the warning message appears once for each annotation on the map, even when the annotations aren't changing but the view is being redrawn due to a change in a published property. I'm giving up on SwiftUI's Map. Going back to MKMapView - better support and all these warnings go away.
Upvotes: 1
Reputation: 824
You should publish your array of items, not the model.
struct TestItem: Identifiable {
let id = UUID()
var name: String
var latitude: Double
var longitude: Double
}
class Model: ObservableObject {
@Published var items = [TestItem]()
private var timer: Timer?
init() {
timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: true) { _ in
Task { @MainActor in
self.addItem()
}
}
}
@MainActor func addItem () {
let name = "Item " + items.count.description
let latitude = Double.random(in: 45...55)
let longitude = Double.random(in: 5...11)
items.append(TestItem(name: name, latitude: latitude, longitude: longitude))
}
}
Now your views will update each time a new item is added.
Upvotes: 1