iosswifticloudcloudkitckrecord

How to save a dictionary (or any other complex structure) into iCloud using Cloudkit?


I need to bind a date to a value and save it into Cloudkit. Rather than just save a new CKRecord for every object and subscript "date" and "value", I would prefer that the CKRecord is an array of dictionary: [(date, value)]. I've looked around and can't find any examples of this type of Cloudkit data storage. I would have thought by now that Codable would have been bridged over to Cloudkit but I don't see anything indicating that. There is a library that handles this but it doesn't handle this type of nesting. Is there anyway to do this?


Solution

  • Although CKRecord does have the ability to support an Array, it is primarily for storing simple data types like strings, numbers, and CKRecord.Reference. You don't specifically call out what your 'value' type is, but here is an example of using JSONEncoder/JSONDecoder to add support for writing/reading any codable type to a CKRecord. The encoder/decoder is simply converting the Encodable/Decodable type to/from a binary Data representation, which CKRecord also supports.

    private let encoder: JSONEncoder = .init()
    private let decoder: JSONDecoder = .init()
    
    extension CKRecord {
        func decode<T>(forKey key: FieldKey) throws -> T where T: Decodable {
            guard let data = self[key] as? Data else {
                throw CocoaError(.coderValueNotFound)
            }
            
            return try decoder.decode(T.self, from: data)
        }
        
        func encode<T>(_ encodable: T, forKey key: FieldKey) throws where T: Encodable {
            self[key] = try encoder.encode(collection)
        }
    }
    

    Usage would look like the following:

    let collection: [[Date: String]] = [[:]]
    let record = CKRecord(recordType: "MyRecord")
    try? record.encode(collection, forKey: "collection")
    let persisted = try? record.decode(forKey: "collection") as [[Date: String]]