Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Obtaining CKAsset URL without downloading data

Tags:

ios

cloudkit

I'm using CloudKit in my app as a way to persist data remotely. One of my records has an CKAsset property that holds an image. When I'm fetching the records, I realized that it's taking so much time to finish the query. After multiple testings, I concluded that when you query records, CloutKit downloads the entire Asset file with the record object. Hence, when you obtain the Asset from the record object and request it's fileURL, it gives you a local file path URL and not an HTTP kind of URL. This is as issue to me because you have to let the user wait so much time for the entire records to be downloaded (with their associated images) before the query ends. Coming from a Parse background, Parse used to give you the HTTP URL and the query is fast enough to load the UI with the objects while loading the images async. My question is, how can I achieve what Parse does? I need to restrict the queries from downloading the Assets data and obtain a link to load the images asynchronously.

like image 297
HusseinB Avatar asked Jan 30 '16 16:01

HusseinB


3 Answers

Make two separate calls for the same record. The first call should fetch all the NON-asset fields you want, and then second request should fetch the required assets.

Something like:

let dataQueryOperation = CKQueryOperation(query: CKQuery(predicate: myPredicate)
dataQueryOperation.desiredKeys = ["name", "age"] // etc
database.addOperation(dataQueryOperation)

let imageQueryOperation = CKQueryOperation(query: CKQuery(predicate: myPredicate)
imageQueryOperation.desiredKeys = ["images"]
database.addOperation(imageQueryOperation)

If need, refactor this into a method so you can easily make a new CKQueryOperation for every asset-containing field.

Happy hunting.

like image 96
Sam Ballantyne Avatar answered Oct 14 '22 15:10

Sam Ballantyne


You can use CloudKit JavaScript for accessing url of asset. Here is an example;

(Used Alamofire and SwiftyJSON)

func testRecordRequest() -> Request {
    let urlString = "https://api.apple-cloudkit.com/database/1/" + Constants.container + "/development/public/records/query?ckAPIToken=" + Constants.cloudKitAPIToken
    let query = ["recordType": "TestRecord"]
    return Alamofire.request(.POST, urlString, parameters: ["query": query], encoding: .JSON, headers: nil)
}

JSON response contains a "downloadURL" for the asset.

"downloadURL": "https://cvws.icloud-content.com/B/.../${f}?o=AmVtU..."

"${f}" seems like a variable so change it to anything you like.

let downloadURLString = json["fields"][FieldNames.image]["value"]["downloadURL"].stringValue
let recordName = json["recordName"].stringValue
let fileName = recordName + ".jpg"
let imageURLString = downloadURLString.stringByReplacingOccurrencesOfString("${f}", withString: fileName)

And now we can use this urlString to create a NSURL and use it with any image&cache solutions such as Kingfisher, HanekeSwift etc. (You may also want to save image type png/jpg)

like image 22
anilgoktas Avatar answered Oct 14 '22 15:10

anilgoktas


Like others have said you cannot get the url(web) of the CKAsset. So your best options are 1. Use a fetch operation with progress per individual UIImageView. I have built a custom one that shows a progress to the user. Cache is not included but you can make a class and adopt NSCoding and save the entire record to cache directory. Here you can see a fetch that i have a completion on to send the asset back to where i call it from to combine it with my other data.

       // only get the asset in this fetch.  we have everything else

let operation = CKFetchRecordsOperation(recordIDs: [myRecordID])

    operation.desiredKeys = ["GameTurnImage"]
        operation.perRecordProgressBlock = {
            record,progress in

            dispatch_async(dispatch_get_main_queue(), {
                self.progressIndicatorView.progress = CGFloat(progress)
            })

        }

    operation.perRecordCompletionBlock = {
        record,recordID,error in
        if let _ = record{
            let asset = record!.valueForKey("GameTurnImage") as? CKAsset

            if let _ = asset{

                    let url = asset!.fileURL
                    let imageData = NSData(contentsOfFile: url.path!)!
                dispatch_async(dispatch_get_main_queue(), {

                    self.image = UIImage(data: imageData)
                    self.progressIndicatorView.reveal()

                })
                completion(asset!)
            }
        }

    }
    CKContainer.defaultContainer().publicCloudDatabase.addOperation(operation)

The other option is to store images on an AWS server or something comparable and then you can use something like SDWebImage to do all of the cache or heavy lifting and save a string in the CKRecord to the image.

I have asked several people about a CKAsset feature to expose a url. I don't know about the JS Api for CloudKit but there might be a way to do it with this but i will let others commment on that.

like image 1
agibson007 Avatar answered Oct 14 '22 14:10

agibson007