2

I've a CKRecord type created in the CloudKit backend with some properties related to that class.

I've String properties, Bytes and I have a Asset List property, so store some images (multiple images related to a single record).

Asset List

Now I'm trying so store some images and then fill the property and then trying to save it to CloudKit, but it's not working.

Code goes as it follows:

var images_array = [CKAsset]()

// append the an image to the array 
images_array.append(CKAsset(fileURL: writeImage(image: selectedImage) as URL))


let record = CKRecord(recordType: recordName)
record["class_title"] = someString as CKRecordValue
record["class_body"]  = someString as CKRecordValue
record["images_array"] = images_array as CKRecordValue
saveRecord(record)

func saveRecord(_ xrecord: CKRecord) {
    let publicData = CKContainer.default().publicCloudDatabase
    let record: [CKRecord] = [xrecord] 
    let saveOperation = CKModifyRecordsOperation.init(recordsToSave: record, recordIDsToDelete: nil)

    saveOperation.perRecordCompletionBlock = {(record, error) -> Void in
        if (error != nil) {
            print("error")
        }
    }
    publicData.add(saveOperation)
}


func writeImage(image: UIImage) -> URL {
    let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    let fileURL = NSURL(fileURLWithPath: documentsURL.absoluteString).appendingPathComponent(".jpg")

    if let imageData = image.lowestQualityJPEGNSData {
        do {
            try imageData.write(to: fileURL!)
        } catch {
            print("ERRO 001 = \(error.localizedDescription)")
        }
    }
    return fileURL!
}

extension UIImage {
    var uncompressedPNGData: Data?      { return UIImagePNGRepresentation(self)        }
    var highestQualityJPEGNSData: Data? { return UIImageJPEGRepresentation(self, 1.0)  }
    var highQualityJPEGNSData: Data?    { return UIImageJPEGRepresentation(self, 0.75) }
    var mediumQualityJPEGNSData: Data?  { return UIImageJPEGRepresentation(self, 0.5)  }
    var lowQualityJPEGNSData: Data?     { return UIImageJPEGRepresentation(self, 0.25) }
    var lowestQualityJPEGNSData:Data?   { return UIImageJPEGRepresentation(self, 0.0)  }
}

If I only save the strings, everything works perfectly but with images it doesn't save the record.

I know there might be any issue with the appending, or I have to save the array in other way, or I shouldn't save it as CKRecordValue.

Do you have any tip on how to achieve this?

Thanks

Ivan Cantarino
  • 3,058
  • 4
  • 34
  • 73
  • I've seen how to save an array of records, but not how to save a Record property array (list) and this is confusing me a lot – Ivan Cantarino Apr 11 '17 at 23:28
  • Do you get an error? What happens? – Paulw11 Apr 11 '17 at 23:35
  • I get the CKError 1, and sometimes no error is presented , just nothing happens. – Ivan Cantarino Apr 11 '17 at 23:39
  • You should also implement the `modifyRecordsCompletionBlock` to get the overall result and print the error description; CloudKit has generally pretty good error messages – Paulw11 Apr 11 '17 at 23:46
  • Nice tip Paul. I'll try that out to see if some more specific error is shown – Ivan Cantarino Apr 11 '17 at 23:48
  • Also, can you show your implementation of `writeImage` ? – Paulw11 Apr 11 '17 at 23:49
  • I've edited the answer - writeImage its a function that retrieves the image url, after processing it to return it in lowest quality to speed up the upload. I could now update a single image but after trying to update 2 images in the array an error in the `modifyRecordsCompletionBlock` prompts with the error `Failed to modify some records` – Ivan Cantarino Apr 12 '17 at 00:08
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/141478/discussion-between-paulw11-and-ivan-cantarino). – Paulw11 Apr 12 '17 at 00:23

2 Answers2

2

When you create your local asset file you should do so with the atomic write option. This will ensure that the file is completely written before CloudKit attempts to upload the asset.

This is the asset file creation function I use in the Seam 3 library:

fileprivate func createAsset(data: Data) -> CKAsset? {

    var returnAsset: CKAsset? = nil

    let tempStr = ProcessInfo.processInfo.globallyUniqueString
    let filename = "\(tempStr)_file.bin"        
    let baseURL = URL(fileURLWithPath: NSTemporaryDirectory())       
    let fileURL = baseURL.appendingPathComponent(filename, isDirectory: false)

    do {
        try data.write(to: fileURL, options: [.atomicWrite])         
        returnAsset = CKAsset(fileURL: fileURL)      
    } catch {
        print("Error creating asset: \(error)")
    }

    return returnAsset

}
Paulw11
  • 108,386
  • 14
  • 159
  • 186
0

You have to take Array of CKAsset for images.

var imageUrls = [CKAsset]()

Now get all images using for-loop. And save CKAsset of images.

for images in self.arrayImageSelected{

                  var myImage = UIImage()
                  if (images.isKindOfClass(PHAsset)){
                        let imageC = images as? PHAsset
                        myImage = self.getAssetThumbnail(imageC!)
                    }else if (images.isKindOfClass(UIImage)){
                        myImage = (images as? UIImage)!
                    }
                    let imagePath = self.storeImageAtDocumentDirectory(myImage, titleName: self.strTitle)
                    myPAth.append(imagePath)
                    let paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
                    let FbPath = paths.stringByAppendingString("/Custom")
                    let filePathToWrite = "\(FbPath)" + imagePath
                    let urls = NSURL(fileURLWithPath: filePathToWrite)
                    let imageAsset = CKAsset(fileURL: urls)
                    imageUrls.append(imageAsset)

                }

Set Your array.

record.setObject(imageUrls, forKey: "images_array")
Kirit Modi
  • 23,155
  • 15
  • 89
  • 112