从用户默认值加载数据时,我得到一个空的 CLLocationCoordinates 数组

Posted

技术标签:

【中文标题】从用户默认值加载数据时,我得到一个空的 CLLocationCoordinates 数组【英文标题】:I get an empty CLLocationCoordinates array when loading data from user defaults 【发布时间】:2018-12-04 14:05:13 【问题描述】:

我正在尝试将来自我的应用程序跟踪部分的 CCLocationCoordinates 数组与作为键的跟踪路线的名称配对存储到 UserDefaults,以便以后能够调用它以在函数中使用它。 问题是当我调用该函数时,我得到索引超出范围错误。我检查了一下,数组是空的。 由于我是用户默认设置的新手,因此我尝试查看其他类似的帖子,但它们都是关于 NSUserDefaults 并且没有找到解决方案。

以下是数组存储和调用函数的代码:

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


    

在扩展中你可以看到使用数组的函数。

在数组打印之后,我得到了索引超出范围错误。 一如既往地感谢社区。​​p>

【问题讨论】:

【参考方案1】:

在尝试了提供的各种解决方案后,我决定重写整个内容。 因此,在找到一篇关于如何将我的数组编码/解码为字符串的帖子后,我决定这是要走的路。它不应该在系统上很重,因为它是一个被保存的字符串。请让我知道您对此解决方案的看法。 感谢@Sh_Khan 指出这是一个解码问题,感谢@Moritz 指出我的做法很糟糕。

所以代码是:

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

【讨论】:

【参考方案2】:

与其使用重量级的objectiv-c-ish NSKeyed(Un)Archiver 并通过CLLocation 绕道而行,我建议扩展CLLocationCoordinate2D 以采用Codable

extension CLLocationCoordinate2D : Codable 
    public init(from decoder: Decoder) throws 
        var arrayContainer = try decoder.unkeyedContainer()
        if arrayContainer.count == 2 
            let lat = try arrayContainer.decode(CLLocationDegrees.self)
            let lng = try arrayContainer.decode(CLLocationDegrees.self)
            self.init(latitude: lat, longitude: lng)
         else 
            throw DecodingError.dataCorruptedError(in: arrayContainer, debugDescription: "Coordinate array must contain two items")
        
    

    public func encode(to encoder: Encoder) throws 
        var arrayContainer = encoder.unkeyedContainer()
        try arrayContainer.encode(contentsOf: [latitude, longitude])
    

并将加载和保存数据的方法替换为

func storeCoordinates(_ coordinates: [CLLocationCoordinate2D]) throws 
    let data = try JSONEncoder().encode(coordinates)
    UserDefaults.standard.set(data, forKey: String(describing: userRoute))


func loadCoordinates() -> [CLLocationCoordinate2D] 
    guard let data = UserDefaults.standard.data(forKey: String(describing: userRoute)) else  return [] 
    do 
        return try JSONDecoder().decode([CLLocationCoordinate2D].self, from: data)
     catch 
        print(error)
        return []
    

storeCoordinatesthrows 它会移交一个潜在的编码错误

加载数据

actualRouteInUseCoordinatesArray = loadCoordinates()

并保存

do 
    try storeCoordinates(actualRouteInUseCoordinatesArray)
 catch  print(error) 

【讨论】:

感谢您的解决方案。不过我想我还是太新手,无法完全理解它,但我会更改代码,因为我没有处理任何可能的错误。【参考方案3】:

你的问题是你把它保存为数据并尝试直接读取而不存档,你可以试试

let locations =  [CLLocation(latitude: 123, longitude: 344),CLLocation(latitude: 123, longitude: 344),CLLocation(latitude: 123, longitude: 344)]

do 

    let archived = try NSKeyedArchiver.archivedData(withRootObject: locations, requiringSecureCoding: true)
    UserDefaults.standard.set(archived, forKey:"myKey")
    // read savely 
    if let data = UserDefaults.standard.data(forKey: "myKey") 
        let saved =  try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data) as! [CLLocation]
        print(saved)
    

catch 

    print(error)

【讨论】:

我不确定如何实施您的解决方案。我用你的代码替换了我的代码的哪一部分? func storeCoordinates()没有正确归档?然后是func loadCoordinates()

以上是关于从用户默认值加载数据时,我得到一个空的 CLLocationCoordinates 数组的主要内容,如果未能解决你的问题,请参考以下文章

为啥我用这些代码得到一个空的 Tableview? JSON

保存/加载 UIColor 用户选择默认值

胶水加载作业不保留红移中的默认列值

使用 swift 将数组保存和检索到用户默认值

更新用户位置或使用默认值时刷新 MKMapView

C# new 后 得到的却是一个空的对象