Search code examples
iosswift4cloudkit

CKAsset not Saved to CloudKit - All other fields are saved


I have a CloudKit app that is basically a master detail setup with one extra feature. Any detail object can be marked as the ActiveNote. When the app is on an iPad, ONLY this ActiveNote is shown (no user interaction). The app includes notifications and subscriptions with all data in a custom zone in the private database. The app works well with one exception.

There are only two record types. All data is stored with type CNote. When a detail item is chosen to be shown on the iPad, I upload that data to a single record of type ActiveNote. The ActiveNote data is used only by the iPad to populate its read only version of the detail view. The iPad dynamically changes whenever the phone user marks a record as active.

All fields are uploaded and correctly shown on the iPad with the exception of the asset for an image. No asset is saved and I receive no error messages. The save procedure for the normal CNotes uses the same procedure but starts with an image from the camera which I reduce in size. This is, of course a UIImage. I cannot get a save from this image. If I change the code to load a static .png included in the app, the upload does work correctly. It is only when trying to upload an image from the DetailViewController. Since the image is originally an asset anyway, I have attempted to reload a CKAsset image directly into the ActiveNote and that does not work either.

Any guidance would be appreciated. Here is the code to save to the single ActiveNote. iOS 11, Xcode 9.3

func queryActiveNote() {

    recordZone = CKRecordZone(zoneName: "CNotes")

    let query = CKQuery(recordType: "ActiveNote", predicate: NSPredicate(format: "TRUEPREDICATE"))
    let sortDescriptor = NSSortDescriptor(key: "noteName", ascending: true)
    query.sortDescriptors = [sortDescriptor]

    privateDatabase.perform(query, inZoneWith: recordZone?.zoneID) { (results, error) in
        if error != nil {
            print("error querying database\(String(describing: error?.localizedDescription))")
        } else {

            guard results?.count > 0 else {return}
            print("results?.count is \(String(describing: results?.count))")

            self.activeNote = results![0]

            self.activeNote!["noteName"] = self.detailItem?["noteName"]

            //save a bunch of other fields too

            let tmpDir = NSTemporaryDirectory()
            let tmpFile = tmpDir.appending("test.png")
            let tmpFileURL = URL(fileURLWithPath: tmpFile)

            //Main queue to avoid runtime warning that image needs to be on main thread
            DispatchQueue.main.async {
                guard let tmpImage = self.imageView.image else {
                    print("guard let tmpImage failed")
                    return
                }

                guard let tmpImageData = UIImageJPEGRepresentation(tmpImage, 1.0) else {
                    print("guard let tmpImageData failed")
                    return
                }

                do {
                    try tmpImageData.write(to: tmpFileURL, options: .atomic)
                    print("the write of chosenImageData succeeded")
                } catch {
                    print("error writing chosenImageData to a file")
                }//do catch

                let asset : CKAsset = CKAsset(fileURL: tmpFileURL)
                self.activeNote!["noteImageData"] = asset

            }//main

            self.privateDatabase.save(self.activeNote!, completionHandler: { (record, error) in
                if error != nil {
                    print("privateDatabase error saving activeNote: \(String(describing: error?.localizedDescription))")
                } else {
                    print("modified activeNote record saved successfully")

                    DispatchQueue.main.async {
                        let ac = UIAlertController(title: nil , message: "Record was successfully saved as Active", preferredStyle: .alert)
                        let okAction = UIAlertAction(title: "Ok", style: .default, handler: nil)
                        ac.addAction(okAction)
                        self.present(ac, animated: true, completion: nil)
                    }//main

                }//if error else
            })//save block

        }//if error else
    }//perform query block

}//queryActiveNote

Solution

  • user3069232 should get credit for the answer. The code above does save the record to CloudKit before the image file save has completed. I moved the CloudKit save block inside the do catch block and the process is successful. Since I'm guaranteed to have an image in all records (there is a default) then making the CloudKit save conditional on having a successful image save is ok.