Hello all!
I was going through the BucketList tutorial and trying to solve challenge 2 when it was behaving oddly. Eventually it worked fine but I don't know why. Can anyone help? Thanks in advance!
The Edit Screen showed blank the first time the "+" button was tapped, but from the 2nd time onwards it worked fine. I tracked it down to the selectedPlace
variable on ContentView being nil, so I added a custom binding to check when it was being set using a print statement:
let selectedPlaceBinding = Binding(
get: { selectedPlace },
set: {
print("ContentView: selected place about to be set")
selectedPlace = $0
})
ZStack {
if isUnlocked {
ComposedMap(selectedPlace: selectedPlaceBinding, showingPlaceDetails: $showingPlaceDetails, centerCoordinate: $centerCoordinate, locations: $locations, showingEditScreen: $showingEditScreen)
With that it began working fine. So then I removed the custom binding from the ComposedMap
call, and it still worked, even though XCode complains the selectedPlaceBinding
was initialised but never accessed:
let selectedPlaceBinding = Binding(
get: { selectedPlace },
set: {
print("ContentView: selected place about to be set")
selectedPlace = $0
})
ZStack {
if isUnlocked {
ComposedMap(selectedPlace: $selectedPlace, showingPlaceDetails: $showingPlaceDetails, centerCoordinate: $centerCoordinate, locations: $locations, showingEditScreen: $showingEditScreen)
However, if I remove the let selectedPlaceBinding
statement altogether it goes back to the weird behaviour. I tried changing it to a random assignment, let a = ""
and the strange behaviour remains.
I tried out in a physical device and the behaviour is similar.
Does anyone have a clue why this happens?
Full code below:
ContentView struct:
struct ContentView: View {
@State private var centerCoordinate = CLLocationCoordinate2D()
@State private var locations = [CodableMKPointAnnotation]()
@State private var selectedPlace: MKPointAnnotation?
@State private var showingPlaceDetails = false
@State private var showingEditScreen = false
@State private var isUnlocked = false
var body: some View {
let selectedPlaceBinding = Binding(
get: { selectedPlace },
set: {
print("ContentView: selected place about to be set")
selectedPlace = $0
})
ZStack {
if isUnlocked {
ComposedMap(selectedPlace: $selectedPlace, showingPlaceDetails: $showingPlaceDetails, centerCoordinate: $centerCoordinate, locations: $locations, showingEditScreen: $showingEditScreen)
} else {
Button("Unlock Places") {
self.authenticate()
}
.padding()
.background(Color.blue)
.foregroundColor(.white)
.clipShape(Capsule())
}
}.onAppear(perform: loadData)
.alert(isPresented: $showingPlaceDetails) {
Alert(title: Text(selectedPlace?.title ?? "Unknown"), message: Text(selectedPlace?.subtitle ?? "Missing Place Information"), primaryButton: .default(Text("OK")), secondaryButton: .default(Text("Edit")) {
showingEditScreen = true
})
}
.sheet(isPresented: $showingEditScreen, onDismiss: saveData) {
if let selectedPlace = selectedPlace {
EditView(placemark: selectedPlace)
} else {
Text("Selected place is nil.")
}
}
}
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
return paths[0]
}
func loadData() {
let filename = getDocumentsDirectory().appendingPathComponent("SavedPlaces")
do {
let data = try Data(contentsOf: filename)
locations = try JSONDecoder().decode([CodableMKPointAnnotation].self, from: data)
} catch {
print("Unable to load saved data.")
print(error.localizedDescription)
}
}
func saveData() {
do {
let filename = getDocumentsDirectory().appendingPathComponent("SavedPlaces")
let data = try JSONEncoder().encode(self.locations)
try data.write(to: filename, options: [.atomicWrite, .completeFileProtection])
} catch {
print("Unable to save data.")
print(error.localizedDescription)
}
}
func authenticate() {
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
let reason = "We need to unlock your data"
context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in
DispatchQueue.main.async {
if success {
self.isUnlocked = true
} else {
// could not auth
}
}
}
} else {
// biometrics not available
}
}
}
ComposedMap struct
struct ComposedMap: View {
@Binding var selectedPlace: MKPointAnnotation?
//@Binding var selectedPlace: MKPointAnnotation?
@Binding var showingPlaceDetails: Bool
@Binding var centerCoordinate: CLLocationCoordinate2D
@Binding var locations: [CodableMKPointAnnotation]
@Binding var showingEditScreen: Bool
var body: some View {
ZStack {
MapView(centerCoordinate: $centerCoordinate, selectedPlace: $selectedPlace, showingPlaceDetails: $showingPlaceDetails, annotations: locations)
.edgesIgnoringSafeArea(/*@START_MENU_TOKEN@*/.all/*@END_MENU_TOKEN@*/)
Circle()
.fill(Color.blue)
.opacity(0.3)
.frame(width: 32, height: 32)
VStack {
Spacer()
HStack {
Spacer()
Button(action: {
let newLocation = CodableMKPointAnnotation()
newLocation.coordinate = centerCoordinate
newLocation.title = "Placeholder Title"
newLocation.subtitle = "Placeholder Subtitle"
locations.append(newLocation)
selectedPlace = newLocation
showingEditScreen = true
}) {
Image(systemName: "plus")
.padding()
.background(Color.black.opacity(0.75))
.foregroundColor(.white)
.font(.title)
.clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/)
.padding(.trailing)
}
}
}
}
}
}