Hello everyone. I am at the end of the BucketList app creation days and I am struggling to resolve a runtime error. In the video it is expected that one will show up ("Publishing changes from within view updates is not allowed, this will cause undefined behavior.") both in the console and as a purple exclamation point.
However, even after implementing the fix using @MainActor
I cannot clear the purple exclamation warning and now a different message is shown in the console:
2022-10-20 09:43:15.490956-0700 BucketList[27327:11286470] [VKDefault] Missing MeshRenderables for ground mesh layer for (4/4) of ground tiles. Tile debug info: (Key: 4.2.3.255 t:34 kt:0, Has mesh errors: 0, MeshInstance count: 3, PendingMaterial count: 3, Invisible MeshInstances count: 0 | Key: 4.3.3.255 t:34 kt:0, Has mesh errors: 0, MeshInstance count: 3, PendingMaterial count: 3, Invisible MeshInstances count: 0 | Key: 3.2.3.255 t:34 kt:0, Has mesh errors: 0, MeshInstance count: 3, PendingMaterial count: 3, Invisible MeshInstances count: 0 | Key: 3.3.3.255 t:34 kt:0, Has mesh errors: 0, MeshInstance count: 2, PendingMaterial count: 2, Invisible MeshInstances count: 0)
This seems like a totally different runtime error than the one highlighted by Paul in the video. The other major difference with this one is that when I click the purple exclamation it does reveal it in the sidebar, but clicking it in the sidebar does not reveal it in the code. So I don't even know where to look.
Anyways, here is my code for ContentView and ContentView-ViewModel. Please let me know if I should post something else:
Note: Googling this console message only gives me results about installing kitchen and bathroom tiles 🤪
struct ContentView: View {
@StateObject private var viewModel = ViewModel()
var body: some View {
if viewModel.isUnlocked {
ZStack {
Map(coordinateRegion: $viewModel.mapRegion, annotationItems: viewModel.locations) { location in
MapAnnotation(coordinate: location.coordinate) {
VStack {
Image(systemName: "star.circle")
.resizable()
.foregroundColor(.red)
.frame(width: 44, height: 44)
.background(.white)
.clipShape(Circle())
Text(location.name)
.fixedSize()
}
.onTapGesture {
viewModel.selectedPlace = location
}
}
}
.ignoresSafeArea()
Circle()
.fill(.blue)
.opacity(0.3)
.frame(width: 32, height: 32)
VStack {
Spacer()
HStack {
Spacer()
Button {
viewModel.addLocation()
} label: {
Image(systemName: "plus")
}
.padding()
.background(.black.opacity(0.75))
.foregroundColor(.white)
.font(.title)
.clipShape(Circle())
.padding(.trailing)
}
}
}
.sheet(item: $viewModel.selectedPlace) { place in
EditView(location: place) { newLocation in
viewModel.update(location: newLocation)
}
}
} else {
Button("Unlock Places") {
viewModel.authenticate()
}
.padding()
.background(.blue)
.foregroundColor(.white)
.clipShape(Capsule())
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import Foundation
import LocalAuthentication
import MapKit
extension ContentView {
@MainActor class ViewModel: ObservableObject {
@Published var mapRegion = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 50, longitude: 0), span: MKCoordinateSpan(latitudeDelta: 25, longitudeDelta: 25))
@Published private(set) var locations: [Location]
@Published var selectedPlace: Location?
@Published var isUnlocked = false
let savePath = FileManager.documentsDirectory.appendingPathComponent("SavedPlaces")
init() {
do {
let data = try Data(contentsOf: savePath)
locations = try JSONDecoder().decode([Location].self, from: data)
} catch {
locations = []
}
}
func save() {
do {
let data = try JSONEncoder().encode(locations)
try data.write(to: savePath, options: [.atomic, .completeFileProtection])
} catch {
print("Unable to save data.")
}
}
func addLocation() {
let newLocation = Location(id: UUID(), name: "New location", description: "", latitude: mapRegion.center.latitude, longitude: mapRegion.center.longitude)
locations.append(newLocation)
save()
}
func update(location: Location) {
guard let selectedPlace = selectedPlace else { return }
if let index = locations.firstIndex(of: selectedPlace) {
locations[index] = location
save()
}
}
func authenticate() {
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
let reason = "Please authenticate yourself to unlock your places."
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in
if success {
Task { @MainActor in
self.isUnlocked = true
}
} else {
// Error
}
}
} else {
// No biometrics
}
}
}
}