Search code examples
iosobjective-ccloudkit

How to update progress with CKModifyRecordsOperation.perRecordProgressBlock


This is related to a recent thread Update progress with MRProgress. I converted my cloudkit queries from the convenience API to CKOperations as a result of previous thread (Thanks Edwin!). So while using CKModifyRecordsOperation to save a record, I can see the record's progress via logging in the perRecordProgressBlock, which is great. However, I'm trying to send this progress back to the viewcontroller and I cannot figure out how to do that. I have created a class for all of my CloudKit methods - CKManager. The other problem I'm having is that I'm unsure when to update the progress indicator (using MRProgress framework) in the VC. Do I call it before, during or after the save operations call in the CKManager? Should it be called recursively until the progress == 1.0? Here is the code I have so far...every works fine except for updating/animating the progress indicator (it appears and shows 0% and then disappears when the save operation is completed). Also, I'm using a property (double progress) in my CKManager class and I know that is incorrect, but I wasn't sure how else to do it. And I do not feel that the callback method I've declared/defined in my CKManager class below for this is correct either. Any guidance is appreciated!

CKManager.h

@property (nonatomic, readonly) double progress;
- (void)recordProgressWithCompletionHandler:(void (^)(double progress))completionHandler;

CKManager.m

    @property (nonatomic, readwrite) double progress;
    - (void)recordProgressWithCompletionHandler:(void (^)(double))completionHandler {

    completionHandler(self.progress);
}

- (void)saveRecord:(NSArray *)records withCompletionHandler:(void (^)(NSArray *, NSError *))completionHandler {

    NSLog(@"INFO: Entered saveRecord...");
    CKModifyRecordsOperation *saveOperation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:records recordIDsToDelete:nil];

    saveOperation.perRecordProgressBlock = ^(CKRecord *record, double progress) {
        if (progress <= 1) {
            NSLog(@"Save progress is: %f", progress);
            self.progress = progress;
        }
    };

    saveOperation.perRecordCompletionBlock = ^(CKRecord *record, NSError *error) {
        NSLog(@"Save operation completed!");
        completionHandler(@[record], error);
    };

    [self.publicDatabase addOperation:saveOperation];
}

Viewcontroller.m - this is from the method that takes the photo from the camera and calls the CKManager class to prepare the record and save it to CK as well as display the MRProgress indicator...

if (self.imageDataAddedFromCamera) {
            self.hud = [MRProgressOverlayView showOverlayAddedTo:self.myCollectionView animated:YES];
            self.hud.mode = MRProgressOverlayViewModeDeterminateCircular;
            self.hud.titleLabelText = UPLOADING_MSG;
            // prepare the CKRecord and save it
            [self.ckManager saveRecord:@[[self.ckManager createCKRecordForImage:self.imageDataAddedFromCamera]] withCompletionHandler:^(NSArray *records, NSError *error) {
                if (!error && records) {
                    NSLog(@"INFO: Size of records array returned: %lu", (unsigned long)[records count]);
                    CKRecord *record = [records lastObject];
                    self.imageDataAddedFromCamera.recordID = record.recordID.recordName;
                    NSLog(@"INFO: Record saved successfully for recordID: %@", self.imageDataAddedFromCamera.recordID);
                    [self.hud dismiss:YES];
                    [self.hud removeFromSuperview];
                    [self.imageLoadManager addCIDForNewUserImage:self.imageDataAddedFromCamera]; // update the model with the new image
                    // update number of items since array set has increased from new photo taken
                    self.numberOfItemsInSection = [self.imageLoadManager.imageDataArray count];
                    [self updateUI];
                } else {
                    NSLog(@"Error trying to save the record!");
                    NSLog(@"ERROR: Error saving record to cloud...%@", error.localizedDescription);
                    [self.hud dismiss:YES];
                    [self.hud removeFromSuperview];
                    [self alertWithTitle:YIKES_TITLE andMessage:ERROR_SAVING_PHOTO_MSG];
                }
            }];
            // where does this call belong?
            [self.ckManager recordProgressWithCompletionHandler:^(double progress) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    NSLog(@"Updating hud display...");
                    [self.hud setProgress:progress animated:YES];
                });
            }];

Solution

  • You should include the progress handler in your saveRecord call like this:

    - (void)saveRecord:(NSArray *)records withCompletionHandler:(void (^)(NSArray *, NSError *))completionHandler recordProgressHandler:(void (^)(double))progressHandler {
    
        NSLog(@"INFO: Entered saveRecord...");
        CKModifyRecordsOperation *saveOperation = [[CKModifyRecordsOperation alloc] initWithRecordsToSave:records recordIDsToDelete:nil];
    
        saveOperation.perRecordProgressBlock = ^(CKRecord *record, double progress) {
            if (progress <= 1) {
                NSLog(@"Save progress is: %f", progress);
                progressHandler(progress)
            }
        };
    
        saveOperation.perRecordCompletionBlock = ^(CKRecord *record, NSError *error) {
            NSLog(@"Save operation completed!");
            completionHandler(@[record], error);
        };
    
        [self.publicDatabase addOperation:saveOperation];
    }
    

    Then you can call that save record like this:

            [self.ckManager saveRecord:@[[self.ckManager createCKRecordForImage:self.imageDataAddedFromCamera]] withCompletionHandler:^(NSArray *records, NSError *error) {
                if (!error && records) {
                    NSLog(@"INFO: Size of records array returned: %lu", (unsigned long)[records count]);
                    CKRecord *record = [records lastObject];
                    self.imageDataAddedFromCamera.recordID = record.recordID.recordName;
                    NSLog(@"INFO: Record saved successfully for recordID: %@", self.imageDataAddedFromCamera.recordID);
                    [self.hud dismiss:YES];
                    [self.hud removeFromSuperview];
                    [self.imageLoadManager addCIDForNewUserImage:self.imageDataAddedFromCamera]; // update the model with the new image
                    // update number of items since array set has increased from new photo taken
                    self.numberOfItemsInSection = [self.imageLoadManager.imageDataArray count];
                    [self updateUI];
                } else {
                    NSLog(@"Error trying to save the record!");
                    NSLog(@"ERROR: Error saving record to cloud...%@", error.localizedDescription);
                    [self.hud dismiss:YES];
                    [self.hud removeFromSuperview];
                    [self alertWithTitle:YIKES_TITLE andMessage:ERROR_SAVING_PHOTO_MSG];
                }
            }, recordProgressHandler:^(double progress) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    NSLog(@"Updating hud display...");
                    [self.hud setProgress:progress animated:YES];
                });
            }];
    

    So that the code for updating the progress is part of your saveRecord call. The code above is not tested by me. So I hope I made no typo's