Search code examples
iosobjective-ccloudkit

CKAccountStatus returning different values when not called from ViewController


I'm using Cloud Kit in my app which makes use of the camera and only allows the user to submit photos if they're logged into iCloud. So when the user clicks the camera button, I call a CloudKit method to get the user's icloud status, which returns a CKAccountStatus value (0-3). I implemented this initially in the view controller and it worked perfectly. Then I did some refactoring and created a CKManager class to house all CK related methods. So now when the camera is clicked instead of calling the CK method off the container directly in the VC, I'm calling it via a method from my CKManager property (which is lazy instantiated). It should only return values 0-3, but it keeps returning 448 for some reason. However, in the CKManager logging, I can see it logging correctly that I'm logged into iCloud. So there's an issue of it translating from there back to the VC. I have feeling this is a threading/callback issue, which I'm not that well versed in.

Can someone take a look at the code and see if there's something obvious I'm doing wrong? Thanks in advance!

- (IBAction)cameraBarButtonPressed:(UIBarButtonItem *)sender {
NSLog(@"Entered cameraBarButtonPressed");

//CKContainer *container = [CKContainer defaultContainer];
dispatch_queue_t fetchQ = dispatch_queue_create("check user status", NULL);
__block CKAccountStatus userAccountStatus;

dispatch_async(fetchQ, ^{ // check user's CK status on different thread
    userAccountStatus = [self.ckManager getUsersCKStatus];
    NSLog(@"cameraBarButtonPressed userAccountStatus: %ld", userAccountStatus);

    if (userAccountStatus == CKAccountStatusAvailable) {
        //NSLog(@"User is logged into CK - user can upload pics!");
        UIImagePickerController *cameraUI = [[UIImagePickerController alloc] init];
        cameraUI.delegate = self; // set the deleage for the ImagePickerController

        // check to see if the camera is available as source type, else check for photo album
        if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
            cameraUI.sourceType = UIImagePickerControllerSourceTypeCamera;
        } else if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeSavedPhotosAlbum]) {
            cameraUI.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
        }

        [cameraUI setAllowsEditing:YES]; // let the user edit the photo
        // set the camera presentation style
        //cameraUI.modalPresentationStyle = UIModalPresentationFullScreen;
        cameraUI.modalPresentationStyle = UIModalPresentationCurrentContext;

        dispatch_async(dispatch_get_main_queue(), ^{ // show the camera on main thread to avoid latency
            [self presentViewController:cameraUI animated:YES completion:nil]; // show the camera with animation
        });
    } else if (userAccountStatus == CKAccountStatusNoAccount) {
        //NSLog(@"User is not logged into CK - Camera not available!");
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"iCloud Not Available" message:@"You must be logged into your iCloud account to submit photos and recipes. Go into iCloud under Settings on your device to login." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
        dispatch_async(dispatch_get_main_queue(), ^{
            [alert show];
        });
    } else if (userAccountStatus == CKAccountStatusRestricted) {
        NSLog(@"User CK account is RESTRICTED !");
    } else if (userAccountStatus == CKAccountStatusCouldNotDetermine) {
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"iCloud Status Undetermined" message:@"We could not determine your iCloud status. You must be logged into your iCloud account to submit photos and recipes. Go into iCloud under Settings on your device to login." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
        dispatch_async(dispatch_get_main_queue(), ^{
            [alert show];
        });
    } else { // did not get back one of the above values so show the Could Not Determine message
        UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"iCloud Status Undetermined" message:@"We could not determine your iCloud status. You must be logged into your iCloud account to submit photos and recipes. Go into iCloud under Settings on your device to login." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil];
        dispatch_async(dispatch_get_main_queue(), ^{
            [alert show];
        });

    }
});
}

The above code is the code that does not work. Here is the code that does work. Just copying the beginning code as the rest is redundant from that point on...

CKContainer *container = [CKContainer defaultContainer];
dispatch_async(fetchQ, ^{ // check user's CK status on different thread
    [container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError *error) {
        if (error) {...

Lastly, here is the code that gets called from CKManager for the code that does not work...

- (CKAccountStatus)getUsersCKStatus {

NSLog(@"Entered getUsersCKStatus...");
__block CKAccountStatus userAccountStatus;

[self.container accountStatusWithCompletionHandler:^(CKAccountStatus accountStatus, NSError *error) {
    if (error) {
        NSLog(@"Error: Error encountered while getting user CloudKit status: %@", error.localizedDescription);
    } else {
        if (accountStatus == CKAccountStatusAvailable) {
            NSLog(@"Info: User is logged into CK - camera is available!");
            userAccountStatus = CKAccountStatusAvailable;
        } else if (accountStatus == CKAccountStatusNoAccount) {
            NSLog(@"Info: User is not logged into CK - Camera not available!");
            userAccountStatus = CKAccountStatusNoAccount;
        } else if (accountStatus == CKAccountStatusRestricted) {
            NSLog(@"Info: User CK account is RESTRICTED - what does that mean!?");
            userAccountStatus = CKAccountStatusRestricted;
        } else if (accountStatus == CKAccountStatusCouldNotDetermine) {
            NSLog(@"Error: Could not determine user CK Account Status: %@", error.localizedDescription);
            userAccountStatus = CKAccountStatusCouldNotDetermine;
        }
    }
}];

NSLog(@"CKAccountStatus: %ld", userAccountStatus);

return userAccountStatus;

}


Solution

  • In the getUsersCKStatus you are calling the accountStatusWithCompletionHandler. That is an asynchronous method. In your case it will return the userAccountStatus before it is set by its callback method.

    You could solve this by making that method synchronous by implementing a semaphore. A better way would be passing on a callback block to that method and not returning a value.