TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

BucketList Challenge 2: Weird Behaviour, SwiftUI Bug?

Forums > 100 Days of SwiftUI

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)
                    }
                }
            }
        }
    }
}

2      

Hi @jmboavida

maybe if you create a new swiftUI View to show the map with all the caracteristics from the ContentView it will be easier. You just need to copy all the code from ContentView.swift, the methods and all the property that you need and put it in the new SwiftUI view. You can let the authenticate method in ContentView and show the new view if the authentication is good. Here my code :

ContentView.swift

import SwiftUI
import LocalAuthentication
import MapKit

struct ContentView: View {
    @State private var isUnlocked = false

    @State private var showAlert = false

    @State private var title = ""
    @State private var message = ""

    var body: some View {
        if isUnlocked {
            ShowMapView()
        } else {
            Button("Unlock Places") {
                self.authenticate()
            }
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .clipShape(Capsule())
            .alert(isPresented: $showAlert) {
                Alert(title: Text(title), message: Text(message), dismissButton: .default(Text("Dismiss")))
            }
        }

    }

    func authenticate() {
        let context = LAContext()
        var error: NSError?
        let reason = "We need to unlock your data"

        if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {

            context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in
                DispatchQueue.main.async {
                    if success {
                        self.isUnlocked = true
                    } else {
                        title = "There is an error"
                        message = authenticationError?.localizedDescription ?? "Unknown error"
                        self.showAlert = true
                    }
                }
            }
        } else {
            context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, error in
                DispatchQueue.main.async {
                    if success {
                        self.isUnlocked = true
                    } else {

                    }
                }
            }
        }
    }

}

ShowMapView.swift

struct ShowMapView: 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

    var body: some View {
        ZStack {
            MapView(centerCoordinate: $centerCoordinate, annotations: locations, selectedPlace: $selectedPlace, showingPlaceDetails: $showingPlaceDetails)
                .edgesIgnoringSafeArea(.all)
            Circle()
                .fill(Color.blue)
                .opacity(0.3)
                .frame(width: 32, height: 32)

            VStack {
                Spacer()
                HStack {
                    Spacer()
                    Button(action: {
                        let newLocation = CodableMKPointAnnotation()
                        newLocation.title = "Example location"
                        newLocation.coordinate = self.centerCoordinate
                        self.locations.append(newLocation)

                        self.selectedPlace = newLocation
                        self.showingEditScreen = true
                    }) {
                        Image(systemName: "plus")
                    }
                    .padding()
                    .background(Color.black.opacity(0.75))
                    .foregroundColor(.white)
                    .font(.title)
                    .clipShape(Circle())
                    .padding([.trailing, .bottom])
                }
            }
        }
        .alert(isPresented: $showingPlaceDetails) {
            Alert(title: Text(selectedPlace?.title ?? "Unknown"), message: Text(selectedPlace?.subtitle ?? "Missing place information"), primaryButton: .default(Text("Dismiss")), secondaryButton: .default(Text("See more")) {
                self.showingEditScreen = true
            })
        }

        .sheet(isPresented: $showingEditScreen, onDismiss: saveData) {
            if self.selectedPlace != nil {
                EditView(placemark: self.selectedPlace!)
            }
        }
        .onAppear(perform: loadData)
    }

    func getDocumentsDirectory() -> URL {
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        return paths[0]
    }

    func loadData() {
        let filename = getDocumentsDirectory().appendingPathComponent("data")

        do {
            let data = try Data(contentsOf: filename)
            locations = try JSONDecoder().decode([CodableMKPointAnnotation].self, from: data)
        } catch {
            print("Unable to load saved data.")
        }
    }

    func saveData() {
        do {
            let filename = getDocumentsDirectory().appendingPathComponent("data")
            let data = try JSONEncoder().encode(self.locations)
            try data.write(to: filename, options: .atomicWrite)
        } catch {
            print("Unable to save data.")
        }
    }
}

Hope that I could help you

2      

Hacking with Swift is sponsored by Superwall.

SPONSORED Superwall lets you build & test paywalls without shipping updates. Run experiments, offer sales, segment users, update locked features and more at the click of button. Best part? It's FREE for up to 250 conversions / mo and the Superwall team builds out 100% custom paywalls – free of charge.

Learn More

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.