Search code examples
swiftcodablecllocationdecodable

Swift - Codable Decode array of arrays of CLLocation


I have a struct that contains an array of arrays of CLLocations. This is to support a multipolyline (in other words, a bunch of potentially discontiguous lines). I wish to encode and decode this data. I am having trouble writing the encoding and decoding methods as CLLocation is not codable by default.

    struct MyTrack {
      let coords: [[CLLocation]]?
    
      enum CodingKeys: String, CodingKey {
        case coords
      }
    }

    extension MyTrack: Decodable {
      init(from decoder: Decoder) throws {
        let values = try decoder.container(keyedBy: CodingKeys.self)
        
        coords = try values.decodeIfPresent([[CLLocation]].self, forKey: .coords)?
            .map { ($0 as AnyObject).map { CLLocation(model: $0) } }
    }
}

Its currently throwing two errors in Xcode:

Cannot convert value of type '[[CLLocation]].Type' to expected argument type '[Any?].Type'

Value of type 'AnyObject' has no member 'map'

Any help much appreciated!



Because CLLocation is not Codable by default, I followed a tutorial to create a wrapper struct around it, the code goes like this:

extension CLLocation: Encodable {
    enum CodingKeys: String, CodingKey {
        case latitude
        case longitude
        case altitude
        case horizontalAccuracy
        case verticalAccuracy
        case speed
        case course
        case timestamp
    }
    public func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(coordinate.latitude, forKey: .latitude)
        try container.encode(coordinate.longitude, forKey: .longitude)
        try container.encode(altitude, forKey: .altitude)
        try container.encode(horizontalAccuracy, forKey: .horizontalAccuracy)
        try container.encode(verticalAccuracy, forKey: .verticalAccuracy)
        try container.encode(speed, forKey: .speed)
        try container.encode(course, forKey: .course)
        try container.encode(timestamp, forKey: .timestamp)
    }
}

struct Location: Codable {
    let latitude: CLLocationDegrees
    let longitude: CLLocationDegrees
    let altitude: CLLocationDistance
    let horizontalAccuracy: CLLocationAccuracy
    let verticalAccuracy: CLLocationAccuracy
    let speed: CLLocationSpeed
    let course: CLLocationDirection
    let timestamp: Date
}

extension CLLocation {
    convenience init(model: Location) {
      self.init(coordinate: CLLocationCoordinate2DMake(model.latitude, model.longitude), altitude: model.altitude, horizontalAccuracy: model.horizontalAccuracy, verticalAccuracy: model.verticalAccuracy, course: model.course, speed: model.speed, timestamp: model.timestamp)
     }
}

Solution

  • You are decoding CLLocation, not your wrapper struct. You should decode your wrapper struct instead. Also, you shouldn't cast to AnyObject.

    extension MyTrack: Decodable {
        init(from decoder: Decoder) throws {
            let values = try decoder.container(keyedBy: CodingKeys.self)
        
            coords = try values.decodeIfPresent([[Location]].self, forKey: .coords)?
                .map { $0.map(CLLocation.init) }
        }
    }