NEW: Learn to build the incredible iOS 15 Weather app today! >>

Day 72 - Project 14: Bucketlist - Unable to load saved data.

Forums > 100 Days of SwiftUI

In the last step of implementing face ID, I'm getting an error that says "Unable to load saved data."

I'm not sure if this is related to this issue but, when clicking the button to authenticate, the face ID detection is not getting triggered either.

A similar issue was reported by @Quindownunder, to which I've already looked into but his solution of tweaking the CodableMKPointAnnotation class didn't seem to work for me.

Here's my code:

ContentView

import LocalAuthentication
import MapKit
import SwiftUI

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 {
        ZStack {
            if isUnlocked {
                MapView(centerCoordinate: $centerCoordinate, selectedPlace: $selectedPlace, showingPlaceDetails: $showingPlaceDetails, annotations: locations)
                    .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)
                    }
                }
            } else {
                Button("Unlock Places") {
                    self.authenticate()
                }
                .padding()
                .background(Color.blue)
                .foregroundColor(.white)
                .clipShape(Capsule())
            }
        }
        .alert(isPresented: $showingPlaceDetails) {
            Alert(title: Text(selectedPlace?.title ?? "Unknown"), message: Text(selectedPlace?.subtitle ?? "Missing place information"), primaryButton: .default(Text("OK")), secondaryButton: .default(Text("Edit")) {
                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("SavedPlaces")
        print(filename)
        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("SavedPlaces")
            print(filename)
            let data = try JSONEncoder().encode(self.locations)
            try data.write(to: filename, options: [.atomicWrite, .completeFileProtection])
            print("Data saved")
        } catch {
            print("Unable to save data.")
        }
    }

    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, autentificationError in

                DispatchQueue.main.async {
                    if success {
                        self.isUnlocked = true
                        print("Unlocked")
                    }
                    else {
                        //error
                    }
                }
            }
        } else {
            //no biometrics
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

MKPointAnnotation-Codable

import Foundation
import MapKit

class CodableMKPointAnnotation: MKPointAnnotation, Codable {
    enum CodingKeys: CodingKey {
        case title, subtitle, latitude, longitude
    }

    override init() {
        super.init()
    }

    required init(from decoder: Decoder) throws {
        super.init()

        let container = try decoder.container(keyedBy: CodingKeys.self)
        title = try? container.decode(String.self, forKey: .title)
        subtitle = try? container.decode(String.self, forKey: .subtitle)

        let latitude = try container.decode(CLLocationDegrees.self, forKey: .latitude)
        let longitude = try container.decode(CLLocationDegrees.self, forKey: .longitude)
        coordinate = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
    }

    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(title, forKey: .title)
        try container.encode(subtitle, forKey: .subtitle)
        try container.encode(coordinate.longitude, forKey: .longitude)
        try container.encode(coordinate.latitude, forKey: .latitude)
    }
}

Any help on this would be very appreciated!

   

Nice!

It's awesome to see you extend an app's capabilities with new functions.

Your faceID code looks very similar to Paul's code in this video: Local Authentication

This video talks about updating the plist to include faceId message. You've done this step? Yes?

But please be clear. You say you're getting an "Unable to load saved data." error. This message is not in your faceId validation code. This error is in your loadData() function.

func loadData() {
   let filename = getDocumentsDirectory().appendingPathComponent("SavedPlaces")
   print(filename)
   do {
      let data = try Data(contentsOf: filename)
      locations = try JSONDecoder().decode([CodableMKPointAnnotation].self, from: data)
      } catch {
          print("Unable to load saved data.")  // This is being executed.
      }
  }

Which line is failing?

  1. let data = try Data(contentsOf:)
  2. locations = try JSONDecoder()

You could be getting data from the file, but not decoding the data into JSON. or You have a filename, but cannot get the data from the file.

Have you verified this yet?

1      

@Obelix, thank you for your reply, you're absolutely right. This is strange as I must not have noticed this issue until after I implemented the face ID.

I went ahead and tried to investigate which line was failing. From what I gathered, getting data from the file seems to be the issue - although keep in mind I'm quite new to Swift and could be wrong. Nonetheless, this brings me to the question of how could I go about handling this issue as I don't know how else to change this line of code...

PS. Thanks for noticing the new functions! With the new updates coming, I find that a lot of functions are being changed and introduced so I try to make an effort and start familiarizing myself with some them now, although most 100 days of SwiftUI lessons still have some older code (for now, since the lessons are beingn updated).

UPDATE:

I couldn't figure out what went wrong in the code so instead I decided to run the code and monitor it in the simulator. The code wouldn't launch and left the screen black, so I deleted the app from the home screen hoping to remove any previously saved data, and upon relaunching the program it seemed to fix the issue. Additionally, the face ID worked without any problem now.

Thank you @Obelix for your help, although there was no problem in the code you helped me understand the real root of the problem at hand so thank you for spending the time of your day to help me out!

1      

Hacking with Swift is sponsored by Essential Developer

SPONSORED Learn the most up-to-date techniques and strategies for testing new and legacy Swift code in this free practical course for iOS devs who want to become complete Senior iOS Developers.

Learn more

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

Reply to this topic…

You need to create an account or log in to reply.

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.