Как сохранить MKPolyline в Core Data с помощью Transformer?

Я хочу сохранить атрибут MKPolyline с помощью Core Data, но единственный ответ, который я смог найти, был старым и использует функции архивирования, устаревшие в iOS 12 (NSKeyedUnarchiver.unarchiveObjectWithData). Они были заменены теми (NSKeyedUnarchiver.unarchivedObject), для которых требуется класс, а MKPolyline не разрешен, поскольку он не соответствует NSCoding. Вот версия, которая больше не поддерживается:

Я настроил атрибут как Transformable в xcdatamodeld и пытаюсь создать функцию Transformer для автоматического преобразования значений. Однако я получаю сообщение об ошибке. Статический метод «unarchivedObject (ofClass: from:)» требует, чтобы «MKPolyline» соответствовал «NSCoding».

Я пробовал несколько способов заставить MKPolyline поддерживать Codable, но я тоже не могу заставить это работать.

Вот мой код преобразователя значений:


import Foundation
import MapKit

@objc(MKPolylineValueTransformer)
public final class MKPolylineValueTransformer: ValueTransformer {

    override public class func transformedValueClass() -> AnyClass {
        return MKPolyline.self
    }

    override public class func allowsReverseTransformation() -> Bool {
        return true
    }
    
    override public func transformedValue(_ value: Any?) -> Any? {
            guard let polyline = value as? MKPolyline else { return nil }
            
            do {
                let data = try NSKeyedArchiver.archivedData(withRootObject: polyline, requiringSecureCoding: true)
                return data
            } catch {
                assertionFailure("Failed to transform `MKPolyline` to `Data`")
                return nil
            }
        }
        
        override public func reverseTransformedValue(_ value: Any?) -> Any? {
            guard let data = value as? NSData else { return nil }
            
            do {
                let polyline = NSKeyedUnarchiver.unarchivedObject(ofClass: MKPolyline, from: data as Data)
                return polyline
            } catch {
                assertionFailure("Failed to transform `Data` to `MKPolyline`")
                return nil
            }
        }
}

extension MKPolylineValueTransformer {

    /// Register the transformer.

    static let name = NSValueTransformerName(rawValue: String(describing: MKPolylineValueTransformer.self))

    public static func register() {
        let transformer = MKPolylineValueTransformer()
        ValueTransformer.setValueTransformer(transformer, forName: name)
    }
}

В качестве альтернативного подхода я взял точки внутри полилинии и создал собственный класс для хранения данных. В основном это работает в том смысле, что я могу успешно сохранить объект, но NSUnarchive возвращает nil, вызывая фатальную ошибку: неожиданно найден nil при распаковке необязательного значения.

Вот код:


import Foundation
import MapKit

public extension MKMultiPoint {
    var coordinates: [CLLocationCoordinate2D] {
        var coords = [CLLocationCoordinate2D](repeating: kCLLocationCoordinate2DInvalid,
                                              count: pointCount)
        getCoordinates(&coords, range: NSRange(location: 0, length: pointCount))
        return coords
    }
}

class CodableCoordinate: NSObject, NSSecureCoding {
    static var supportsSecureCoding: Bool = true
    
    var latitude: Double = 0
    var longitude: Double = 0
    
    init(latitude: Double, longitude: Double) {
        self.latitude = latitude
        self.longitude = longitude
    }
    
    required init(coder aDecoder: NSCoder) {
        super.init()
        
        latitude = aDecoder.decodeDouble(forKey: "latitude")
        longitude = aDecoder.decodeDouble(forKey: "longitude")
    }

    func encode(with coder: NSCoder) {
        coder.encode(latitude, forKey: "latitude")
        coder.encode(longitude, forKey: "longitude")
    }

}

class CodableCoordinatesArray: NSObject, NSSecureCoding {
    static var supportsSecureCoding: Bool = true
    var coordinatesArray: [CodableCoordinate]

    init(coordinatesArray: [CodableCoordinate]) {
        self.coordinatesArray = coordinatesArray
    }
    
    required init(coder aDecoder: NSCoder) {
        let x = aDecoder.decodeObject(forKey:"coordinatesArray")
        coordinatesArray = aDecoder.decodeObject(forKey: "coordinatesArray") as! [CodableCoordinate]
    }
    
    convenience init(fromMKPolyline polyline: MKPolyline) {
        self.init(coordinatesArray: [CodableCoordinate]())
        for coord in polyline.coordinates {
            coordinatesArray.append(CodableCoordinate(latitude: coord.latitude, longitude: coord.longitude))
        }
    }
 
    func encode(with coder: NSCoder) {
        coder.encode(coordinatesArray, forKey:"coordinatesArray")
    }

    func polyline() -> MKPolyline? {
        if coordinatesArray.isEmpty { return nil }
        var coords = [CLLocationCoordinate2D]()
        for coord in coordinatesArray {
            coords.append(CLLocationCoordinate2D(latitude: coord.latitude, longitude: coord.longitude))
        }
        return MKPolyline(coordinates: coords, count: coords.count)
    }
}


@objc(MKPolylineValueTransformer)
public final class MKPolylineValueTransformer: ValueTransformer {

    override public class func transformedValueClass() -> AnyClass {
        return MKPolyline.self
    }

    override public class func allowsReverseTransformation() -> Bool {
        return true
    }
    
    override public func transformedValue(_ value: Any?) -> Any? {
            guard let polyline = value as? MKPolyline else { return nil }
            
            do {
                let arrayFromPolyline = CodableCoordinatesArray(fromMKPolyline: polyline)
                let data = try NSKeyedArchiver.archivedData(withRootObject: arrayFromPolyline, requiringSecureCoding: true)

                return data
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
        
        override public func reverseTransformedValue(_ value: Any?) -> Any? {
            
            guard let data = value as? NSData else { return nil }
            
            do {
                let coordinatesArray = try NSKeyedUnarchiver.unarchivedObject(ofClass: CodableCoordinatesArray.self, from: data as Data)
                return coordinatesArray?.polyline()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
}

extension MKPolylineValueTransformer {
    static let name = NSValueTransformerName(rawValue: String(describing: MKPolylineValueTransformer.self))

    /// Registers the value transformer
    public static func register() {
        let transformer = MKPolylineValueTransformer()
        ValueTransformer.setValueTransformer(transformer, forName: name)
    }
}

К вашему сведению, следующая строка включена, чтобы позволить мне проверить декодирование перед кастингом:

        let x = aDecoder.decodeObject(forKey:"coordinatesArray")

Спасибо


person Jim Sutcliffe    schedule 22.03.2021    source источник