How to resolve CloudKit functionality inconsistency between devices - ios

I've integrated CloudKit into my iOS app and I'm encountering functionality inconsistency between devices.
One of my users has an iPod 5th Gen and an iPhone 6. If they use the iPod everything works as expected. However if they use the iPhone 6 they can only receive data and notifications, they can't make changes of their own. When the iPhone attempts to make changes to the iCloud public database I receive no errors and changes are made locally, they just never make it to the server (everything appears to work, it just doesn't).
Given that the user can use the iPod successfully but the same iCloud account only receives data on the iPhone suggests to me that this might be a permissions or settings issue.
I've checked:
User is logged into iCloud
iCloud drive is on
The app appears enabled in the iCloud Drive menu in settings
The app appears enabled under the "Look Me Up By Email" menu
Other details:
The app is available through TestFlight
The app is using the production container (required for TestFlight)
I don't know what code to supply because the app functions as expected on other devices but let me know what could help. Any help would be greatly appreciated.

This was resolved by changing my modify operations qualityOfService property to .UserInitiated.
After I reviewed the completion handler for my CKModifyRecordsOperation I realized that I was saving data necessary for local functionality regardless of whether the completion handler received an error or not (and incidentally, whether it was executed or not). So when it appeared to me that my operation was being run and returning no errors, it actually wasn't being run at all. Changing the qualityOfService appears to have resolved this.
Answer that led to the solution

Related

Failing to receive NSUbiquitousKeyValueStoreInitialSyncChange

I have a iPhone/iPad App written in Objective-C.
My App shares data between multiple devices via the iCloud using the Key-Value Storage data model. This logic has all worked now for literally years.
Yesterday, I cleared my App from my iPhone A, I reinstalled the App onto iPhone A and then I enabled iCloud Sharing on iPhone A.
After enabling iCloud data, as expected, a full copy of my most recent data downloaded onto iPhone A from the iCloud. Everything looked good.
But later, when I modified the data on iPhone A and attempted to share it with the other devices via iCloud, I got an error message.
The error message warned me that until I had received an InitialSyncChange notification from iCloud, it was unwise for me to propagate the data on iPhone A through the iCloud and onto my other devices.
Long ago, when I wrote this iCloud code, I had set it up so that it would refuse to send data out to the iCloud unless it had previously set a flag indicating that it had seen an incoming InitialSyncChange notification.
But what puzzled me here was that I had seen a copy of my most recent shared data downloaded onto iPhone A from the iCloud after I had enabled iCloud sharing.
When I looked into this and repeated the situation several times, I discovered that when the initial copies of my shared iCloud data were arriving on iPhone A, they were arriving with StoreServerChange notifications and not with InitialSyncChange notifications.
I have no idea why this has changed. Has anyone else seen something similar?
I have an obvious and easy fix for the problem but, in truth, I really hate applying fixes when I don't fully understand why things have changed.
My fix, if you are curious, would be to change how I set the flag that records if I've seen an incoming InitialSyncChange notification.
Now I will set it when I've seen either a InitialSyncChange notification or StoreServerChange notification.
Either notification 'proves' that I've got a valid copy of the shared data. And if as the flag is not set, then the existing logic prevents the App from sending out bogus data and messing with the valid shared data.
Any thoughts or trouble-shooting advice would be appreciated.

Silently Updating iOS Enterprise Apps in Single App Mode

I have a need to update my managed app running on a large amount of iPads without any user interaction. These devices all have the managed app locked in Single App Mode. As I understand it, it's an iOS limitation that an app cannot be updated if either of the following conditions are present:
The app to be updated is in the foreground.
Any app is locked in Single App mode.
With our use case, both of these conditions are present. I also understand that the current workaround is to do the following:
Disable Single App Mode.
Enable Single App Mode for a different app (e.g. Safari) to bring that app into the foreground.
Disable Single App Mode.
Update app.
Re-enable Single App Mode for your app when the update finishes to bring it back into the foreground.
While this method works, it has a lot of drawbacks:
It's highly manual, we have not found a way to automate it. This is especially a problem as the number of devices becomes large. Internet connections can be spotty and there is no robust way to ensure that each step has executed for all devices.
It's prone to failure. Especially between steps 4 and 5 above. It seems there are little to no guarantees of when/if an update was successful to know definitively when to re-enable Single App Mode.
It fails for devices that are offline at time of update. If a device does not have internet connection when the update is executed, there is no way to guarantee that the steps execute fully and in the correct order the next time the device comes back online.
I am looking for a scalable solution to reliably update my managed, Single App Mode app running on thousands of devices in an automated way with no user interaction. I am using Meraki for an MDM right now, for what it's worth.
With iOS 11.2 you can now push a silent update to a device with it running SingleApp mode.
Apple failed to mention this to the public but is included in the beta notes.
Called Enterprise Support today, They could not find any official documentation mentioning ios 11.2.x supports the updating of apps while in single app mode :(
Now with iOS 11.3 beta it is working again but after updation, app is not automatically locking in Single App mode we have to restart device.
Hope in upcoming beta's this issue will get addressed.
iOS 13.1 beta 1 seems to fix most of the issues I have been having in this area.
I had seen issues in iOS 11-12.* where updates sometimes worked, but sometimes showed a pin-pad even though no passcode or guided access code was set, or otherwise behaved strangely.
In the 13.1 beta 1 you can push an App update and nothing will happen on the device, but if you send a restart command afterwards the device will restart, update the App and continue in single app mode.
I have tested with both 'Single App Mode' (SAM) and 'Autonomous Single App Mode' (ASAM) and it has worked every time for me so far.
This works for my use case as you can push the update anytime and schedule the restart at suitable time when the device is not in use.
edit: 13.1 beta 2/3/4 changes this, now when you push an update from your MDM the App will close, update and re-open and continue in single App mode. I checked both SAM and ASAM both now update without the need for a restart.

NSURLSessionDelegate methods not called in TestFlight installs

I am building an app targeting iOS 8.0 that needs to download files that are up to 250Mb. I had a version of the download code roughly working using Alamofire but I recently replaced that with a pure NSURLSession implementation. This new implementation is working as expected in the simulator and on my iPhone 5S running iOS 9.2 (13C75). It works on my phone whether I install through XCode and a physical connection or through TestFlight. I have deleted the app, restarted the phone, and reinstalled and it always completes downloads correctly. One of my colleagues pulled the code and was able to successfully simulate it from XCode.
However, downloads fail on all of my collaborators' devices. They are installing the app through TestFlight and are set up as internal testers. One collaborator also has a iPhone 5S running iOS 9.2 (13C75). The Alamofire-based implementation worked as intended when deployed through TestFlight to those same devices and very little other code has changed.
I have added some remote logging and I can see that on the failing devices, downloads are correctly triggered and the download tasks I create each have a taskIdentifier which I can log. However, none of the NSURLSessionDelegate or NSURLSessionDownloadDelegate methods are called.
What suggestions do you have for troubleshooting?
Is it possible that this could be related to a TestFlight problem? My current next step is to try an alternative to TestFlight. Ultimately, I would like to be able to deploy betas through TestFlight if possible.
I'm using Swift 2.1.1 in XCode 7.2 (7C68).
The problem turned out to be that I had set the NSURLSessionConfiguration discretionary property to true. The docs state the following which sounded good to me:
When transferring large amounts of data, you are encouraged to set the
value of this property to true.
But I failed to appreciate the consequences of the rest of the text:
For example, the system might delay transferring large files until the device is plugged in and connected to the network via Wi-Fi.
The reason that installations through XCode were working was presumably that the phone was either being charged because it was connected to my computer when I started a download or had just been charged.
I initially suspected that the problem came from either TestFlight or something about the state of the phone. Remote logging with Sentry (which I already use for the backend) was very helpful for getting some insights into what was different when my collaborators ran the app. I eventually added in implementations for all the NSURLSessionDownloadDelegate methods to log which ones were called. This led me to see that didReceiveChallenge was the only delegate method being called and I then spent some time thinking that it could be an auth-related problem and troubleshooting in that general area.
Finally, I started noticing that my own install would fail sometimes and one collaborator had a successful download. That made me start thinking about the kind of phone state that could prevent a download. I went back to the NSURLSession configuration that I had and that's when I read about the discretionary property.
In retrospect, carefully checking the configuration would have been a good step to do earlier. Another good thing to do would have been to log the state property of the download tasks that were created.

Core Data - iCloud behavior

I am implementing iCloud support in my core data app using an NSManagedDocument as the core data stack.
Everything seems to be working fine but with some unexpected behavior:
With the universal app installed on my iPhone and iPad:
an entity added from the iPhone gets iClouded to the iPad;
an entity added from the iPad gets iClouded to the iPhone;
the entity added from the iPhone, deleted on the iPhone is automatically deleted on the iPad
the entity added from the iPad, deleted on the iPad is automatically deleted on the iPhone
However ...
the entity added from the iPhone, deleted on the iPad is not deleted on the iPhone
the entity added from the iPad, deleted on the iPhone is not deleted on the iPad
Is this the expected behavior? This is not how the Apple 'notes' app behaves on my iPhone and iPad. Any changes to any notes are reflected across all devices.
If this is not the correct iCloud behavior, any ideas where I might have gone astray?
Thanks so much.
Is this the expected behavior?
That depends on how you define "expected". It's not how Apple intends it to work, but you're not the first person to run into this problem. In practice, yeah, it's expected at least some of the time.
A couple of things you should check:
Does this condition persist even if you quit and re-launch the app? It could be that the instance is getting deleted but that your app isn't updating its UI properly. That would make it appear that the object was still there, until you relaunched the app.
Take a look at the "did import" notification you receive from iCloud. It should contain object IDs for inserted, updated, and deleted instances. Make sure it correctly indicates which object(s) have been deleted on the receiving end.
Check out your object handling to make sure you're not somehow holding on to the object that should have been deleted, or re-creating it.
But keep in mind, if you're using Core Data with iCloud, you are asking for trouble and you may not be able to fix this. Core Data's iCloud integration is still not all that reliable. Expect problems, potentially too severe to fix.

Questions concerning iCloud + core data

I have an app on the app store, that uses coredata as storage. I wan't to update the app with iCloud synchronization as new feature. Following apple`s sample code, I managed to have my core data storage synchronize between devices.
However, I'm experiencing problems when either iCloud synchronization is turned off/on in the app on only one of the devices, or when the app is deleted from the device and the reinstalled. In both cases, data is not synchronized back to the device, although it is available just fine on a second device (which was not disabled/reinstalled).
I also found that all storage is effectively erased completely, when I delete the app from all devices, and then reinstall. Althrough I get a couple of merge notifications in the console (even some without errors), I can't see no data in the local storage of the device.
Browsing the mobile documents folders on my mac still reveals lots of transaction logs in the icloud storage of my app.
Even deleting the app from all devices and starting from scratch wont sort things out. I will end up in a situation where data is either only synced to one device, or not synced at all.
I wonder if there is anything I can do about this inconsistent state that is created when only one device is temporarily iCloud disabled, or the app is deleted from ONE device?
As for my code, its an 1:1 copy of the recipces example from apple.
Daniel Pasco talked about using Core Data and iCloud together at NSConference 2012. Some notes from that blog post:
launching with -com.apple.coredata.ubiquity.logLevel 3 to get a spamfest in the message log saying what Core Data and iCloud are doing.
The conclusion from this talk appears to be that using Core Data and iCloud are really not ready for each other at this stage.
He posted an updated Core Data Recipes project on Github which may or may not fix your problem.
Apple makes it seem easy, but there are a number of nuances with regard to correctly seeding iCloud with data, and what happens afterwards when iCloud support is toggled on and off on different devices.
I implemented a sample project that demonstrates a straightforward way to add iCloud support to Library-style CoreData apps. It's called iCloudStoreManager and it's available on github.
I'm still testing it before I add iCloud support to one of my own production apps. It's working, but I see unexpected errors and delays when an iPad 3 is in the mix. It works, but with long delays.
I've also tested with iPhone 4, iPhone 4S, and the original iPad, and any mix of those devices works well in my experience.

Resources