Search code examples
iosswiftuserdefaults

I get an empty CLLocationCoordinates array when loading data from user defaults


I'm trying to store to UserDefaults an array of CCLocationCoordinates from the tracking portion of my app paired with the name of the tracked route as key, to be able to recall it later on to use it within a function. The problem is that when I call that function I get the index out of range error. I checked and the array is empty. As I'm new to user defaults I tried to see other similar posts but they're all about NSUserDefaults and didn't find a solution.

Heres the code for the functions for storing and recalling the array:

func stopTracking2() {

        self.trackingIsActive = false
        self.trackigButton.backgroundColor = UIColor.yellow
        locationManager.stopUpdatingLocation()
        let stopRoutePosition = RouteAnnotation(title: "Route Stop", coordinate: (locationManager.location?.coordinate)!, imageName: "Route Stop")
        self.actualRouteInUseAnnotations.append(stopRoutePosition)



        print(actualRouteInUseCoordinatesArray)
        print(actualRouteInUseAnnotations)
        drawRoutePolyline()      // draw line to show route
//        checkAlerts2()           // check if there is any notified problem on our route and marks it with a blue circle, now called at programmed checking

        saveRouteToUserDefaults()
        postRouteToAnalitics() // store route anonymously to FIrebase

    }

    func saveRouteToUserDefaults() {

        // save actualRouteInUseCoordinatesArray : change for function
//        userDefaults.set(actualRouteInUseCoordinatesArray, forKey: "\(String(describing: userRoute))")

        storeCoordinates(actualRouteInUseCoordinatesArray)

    }

    // Store an array of CLLocationCoordinate2D
    func storeCoordinates(_ coordinates: [CLLocationCoordinate2D]) {
        let locations = coordinates.map { coordinate -> CLLocation in
            return CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
        }
        let archived = NSKeyedArchiver.archivedData(withRootObject: locations)
        userDefaults.set(archived, forKey: "\(String(describing: userRoute))")

        userDefaults.synchronize()
    }

    func loadRouteFromUserDefaults() {
        // gets entry from userRouteArray stored in userDefaults and append them into actualRouteInUseCoordinatesArray
        actualRouteInUseCoordinatesArray.removeAll()
        actualRouteInUseCoordinatesArray = userDefaults.object(forKey: "\(String(describing: userRoute))") as? [CLLocationCoordinate2D] ?? [CLLocationCoordinate2D]() // here we get the right set of coordinates for the route we are about to do the check on

        // load route coordinates from UserDefaults


//        actualRouteInUseCoordinatesArray = loadCoordinates()! //error found nil

    }


    // Return an array of CLLocationCoordinate2D
    func loadCoordinates() -> [CLLocationCoordinate2D]? {
        guard let archived = userDefaults.object(forKey: "\(String(describing: userRoute))") as? Data,
            let locations = NSKeyedUnarchiver.unarchiveObject(with: archived) as? [CLLocation] else {
                return nil
        }

        let coordinates = locations.map { location -> CLLocationCoordinate2D in
            return location.coordinate
        }

        return coordinates
    }

}



extension NewMapViewController {

    // ALERTS :

    func checkAlerts2() {

        loadRouteFromUserDefaults()        //load route coordinates to check in
        // CHECK IF ANY OBSTACLE IS OUN OUR ROUTE BY COMPARING DISTANCES

        while trackingCoordinatesArrayPosition != ( (actualRouteInUseCoordinatesArray.count) - 1) {
            print("checking is started")
            print(actualRouteInUseCoordinatesArray)
            let trackingLatitude = actualRouteInUseCoordinatesArray[trackingCoordinatesArrayPosition].latitude
            let trackingLongitude = actualRouteInUseCoordinatesArray[trackingCoordinatesArrayPosition].longitude
            let alertLatitude = alertNotificationCoordinatesArray[alertNotificationCoordinatesArrayPosition].latitude
            let alertLongitude = alertNotificationCoordinatesArray[alertNotificationCoordinatesArrayPosition].longitude

            let coordinateFrom = CLLocation(latitude: trackingLatitude, longitude: trackingLongitude)
            let coordinateTo = CLLocation(latitude: alertLatitude, longitude: alertLongitude)
            let coordinatesDistanceInMeters = coordinateFrom.distance(from: coordinateTo)

            // CHECK SENSITIVITY: sets the distance in meters for an alert to be considered an obstacle
            if coordinatesDistanceInMeters <= 10 {

                print( "found problem")
                routeObstacle.append(alertNotificationCoordinatesArray[alertNotificationCoordinatesArrayPosition]) // populate obstacles array
                trackingCoordinatesArrayPosition = ( trackingCoordinatesArrayPosition + 1)

            }
            else if alertNotificationCoordinatesArrayPosition < ((alertNotificationCoordinatesArray.count) - 1) {

                alertNotificationCoordinatesArrayPosition = alertNotificationCoordinatesArrayPosition + 1
            }
            else if  alertNotificationCoordinatesArrayPosition == (alertNotificationCoordinatesArray.count - 1) {

                trackingCoordinatesArrayPosition = ( trackingCoordinatesArrayPosition + 1)
                alertNotificationCoordinatesArrayPosition = 0
            }

        }

        findObstacles()

        NewMapViewController.checkCounter = 0


        displayObstacles()


    }

In the extension you can see the function that uses the array.

Right after the print of the array I get the index out of range error. Thanks as usual to the community.


Solution

  • After trying various solutions offered I decided to rewrite the whole thing. So after finding a post on how to code/decode my array to string I decided it was the way to go. It shouldn't be heavy on the system as it's a string that gets saved. Please let me know what you think of this solution. Thank to @Sh_Khan to point out it was a decoding issue, and to @Moritz to point out I was performing a bad practice.

    So the code is:

    func storeRoute() {
        // first we code the CLLocationCoordinate2D array to string
    
    
    
        // second we store string into userDefaults
    
        userDefaults.set(encodeCoordinates(coords: actualRouteInUseCoordinatesArray), forKey: "\(String(describing: NewMapViewController.userRoute))")
    }
    
    func loadRoute() {
    
        //first se load string from user defaults
        let route = userDefaults.string(forKey: "\(String(describing: NewMapViewController.userRoute))")
        print("loaded route is \(route!))")
    
        //second we decode it into CLLocationCoordinate2D array
        actualRouteInUseCoordinatesArray = decodeCoordinates(encodedString: route!)
        print("decoded route array is \(actualRouteInUseCoordinatesArray))")
    
    }
    
    func encodeCoordinates(coords: [CLLocationCoordinate2D]) -> String {
        let flattenedCoords: [String] = coords.map { coord -> String in "\(coord.latitude):\(coord.longitude)" }
        let encodedString: String = flattenedCoords.joined(separator: ",")
        return encodedString
    }
    func decodeCoordinates(encodedString: String) -> [CLLocationCoordinate2D] {
        let flattenedCoords: [String] = encodedString.components(separatedBy: ",")
        let coords: [CLLocationCoordinate2D] = flattenedCoords.map { coord -> CLLocationCoordinate2D in
            let split = coord.components(separatedBy: ":")
            if split.count == 2 {
                let latitude: Double = Double(split[0]) ?? 0
                let longitude: Double = Double(split[1]) ?? 0
                return CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
            } else {
                return CLLocationCoordinate2D()
            }
        }
        return coords
    }