CloudKit Notification for Checking if User Upgraded iCloud Storage Space for error handling?

So working on an app that uses CloudKit (private database). Now, the app is allowed to run in "local mode" if the user is not using iCloud or has disabled iCloud for my app.


Now imaging this case:

1) iCloud is not enabled for my app and the user starts creating data.

2) The user subsequently enables iCloud for my app.

3) My app gets a CKAccountChangedNotification.

4) My app starts uploading local data the user created before iCloud was enabled to CloudKit.

5) My app gets a CKErrorQuotaExceeded when uploading data.

6) My app displays an alert, telling the user he has run out of iCloud storage and he can either Buy More in iCloud Settings or turn off iCloud for the app to run in "Local mode."


So now my app is in this state where iCloud is enabled but the user has no iCloud storage left. I don't see any API to tell if iCloud storage changed. If the user goes to Settings, buys more storage, and goes back to my app, I'd like to retry sending the local data back to Cloudkit for initial sync. I guess I just have to retry the operation every time the app is launched and show the error over and over again until the user does something about it (disable iCloud in Settings or buy more storage)?


Ideally, I'd like to give the user an option to turn off iCloud (for my app only) in app when the error is hit, because telling them to navigate to Settings to turn off iCloud is hard for a lot of people. Or present a system provided view controller offerring them to upgrade their iCloud storage plan. CKStoragePurchaserViewController?


#pragma mark - CKStoragePurchaserViewControllerDelegate
-(void)storagePurchaserViewControllerDidPurchaseAdditionalStorage:(CKStoragePurchaserViewController*)vc
{
   //retry sync.
}

-(void)storagePurchaserViewControllerDidDisableICloudFunctionality:(CKStoragePurchaserViewController*)vc
{
    //iCloud is now off, run the app in local mode.
}

Replies

From a UX perspective, I'd only tell them they hit their quota a single time, give them the option to hop into Settings or disable iCloud syncing within your app, and leave it at that. At most, after explicitly tellng them the first time, I'd just show some sort of icon near the relevant setting (SF symbol exclamationmark.icloud would work well).


Then I'd say it's safe to just keep hitting CloudKit with the same request until it succeeds, keeping any additional errors silenced.

You are already starting out this way:


1) iCloud is not enabled for my app and the user starts creating data.

2) The user subsequently enables iCloud for my app.


If you hit that 'quota exceeded' error then issue an alert to the user saying that they are being switched back to the default setting. Switch them back (#1) so that they have to do #2 again. No need to test anything.

>Then I'd say it's safe to just keep hitting CloudKit with the same request until it succeeds, keeping any additional errors silenced.


This may be the only way to deal with the issue under the current API. Would be nice to offer them an option to purchase additional storage or at least turn off iCloud in app or to do something other than manually explaining to the user their options: "You need to go to Settings -> iCloud -> App name and turn off iCloud for this app." Unless there is an API to direct them the proper location in Settings I can call from a button in the alert, but I don't think there is? Having them leave the app isn't a great experience either though.


I could have my own preference to turn off iCloud in app when iCloud is enabled so I could workaround this so I'd treat the user as having iCloud disabled if he disables it in *my ui* (lots of people don't know how to turn off iCloud for a particular app). But then I'm adding an extra layer of complexity on the user if he ever bought more storage later and wanted to re-enable it (has to turn on my preference, and manage the one in Settings). So yeah, I guess I just have to keep hitting Cloudkit and show the out of storage error. I probably would display it more than once (because people forget, why isn't this working?!). Maybe once a week. Once a month?


>At most, after explicitly tellng them the first time, I'd just show some sort of icon near the relevant setting (SF symbol exclamationmark.icloud would work well).


Having to carve out UI for this is a decent amount of extra work. Not sure what Setting I'd put it next to, since my app cannot provide any UI that toggles iCloud availability (as mentioned above, adding an extra preference on top of iCloud preference in Settings is not exactly ideal). Would be nice if I could pop the icon in the status bar.

>If you hit that 'quota exceeded' error then issue an alert to the user saying that they are being switched back to the default setting. Switch them back (#1) so that they have to do #2 again. No need to test anything.


Yes, but there's no way (as far as I can tell) to disable iCloud from within my app (since that is managed in the Settings app). So switching them back to #1, requires me to add my own local preference to toggle iCloud. This would be confusing to some users if they turned off my preference, and then maybe a week later they bought more iCloud storage. Then they forgot about my local preference. So then iCloud for my app is enabled in Settings. But my local preference is out of sync with the system preference. This is putting a lot on the user, and I'd like the behavior of my app to be easy.


I think the only option is to just keep retrying Cloudkit sync and silently failing, since there's no reasonable way to tell what the user would like to do.

I don't think you need to worry about them leaving the app if you're explicitly giving them an action to perform that can only been done outside of it; they'll come back as long as the task they leave the app for is in the same vein of work and not very complicated. I remember there was once a URL scheme to pop a user into the Account settings, but I'm not too sure if it's still working at this point.


Foregoing an actual toggle for enabling and disabling iCloud within your app independent of the system controls, I'd really recommend having some sort of view that can pop into your settings (or somewhere within your UI) whenever there's an unexpected issue with cloud services. While not the same situation, Apple's Setting's app will put a rows at the top of the list when there's immediate action that needs to be taken by the user (like Billing Issues, Updates Pending, see: payment-and-shipping-settings--467x500.jpg). Those dialogs are pretty simple and just work towards helping the user solve the problem.


While iCloud should be seamless, problems like this should be available for the user to fix at their own digression after telling them the first time. Otherwise your users will get frustrated with the experience and drop off when they can't find any sort of UI mechanism reflecting that something isn't working to help match their interpretations. Throttling the alert dialogs is a good idea, but each time you show it and the user actively doesn't take action, the less impact it'll have on each reappearance.

TMI. But they don't 'leave the app' if you:


                [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"App-Prefs:"] options:[NSDictionary dictionary] completionHandler:nil];

TMI. Doesn't the app determine whether or not you use iCloud for most uses of iCloud?

>TMI. Doesn't the app determine whether or not you use iCloud for most uses of iCloud?


Not sure what you mean? The app can start uploading stuff to the user's private iCloud account. The user can disable iCloud for a particular app in Settings. I can duplicate the preference in my app. Then just run in local mode if


if (!iCloudDisabledLocally || accountStatus == CKAccountStatusNoAccount || accountStatus == CKAccountStatusRestricted)
{      
//local mode
}

it seems dumb to duplicate the preference in app. And it seems dumb to keep hitting Cloudkit when the user is out of space and is essentially running the app in local mode. I get that there seems to be no other choice but to do this (the API is what it is) but still think that they should think about improving this in the future.

I see now what you are doing. Does this help....


https://developer.apple.com/library/archive/documentation/General/Conceptual/iCloudDesignGuide/Chapters/iCloudFundametals.html


If a user signs out of iCloud, such as by turning off Documents & Data in Settings, the value of the

ubiquityIdentityToken
property changes to
nil
. To detect when a user signs in or out of iCloud, register as an observer of the
NSUbiquityIdentityDidChangeNotification
notification, using code such as that shown in Listing 1-3. Execute this code at launch time or at any point before actively using iCloud.

> Then I'd say it's safe to just keep hitting CloudKit with the same request until it succeeds, keeping any additional errors silenced.

Here I see that you are able to listen if quota is exceeded or if the sync is successful or not. Is it a manual sync you are doing to the CloudKit or is the container configured as NSPersistentCloudKitContainer?

If it is the later, how are you able to listen to these status callbacks?

Any leads would be appreciated.
TIA