icloud core data sync - ios

I am starting out with building a iOS app which will utilize core data and will sync its data with other devices through iCloud. I was thinking of giving a "Sync" button so that the data is not modified at random moments by data from iCloud. The following situation would then occur :-
User A is working on device A and adds two records. User B is currently working on device B and does not press the Sync Button. At night, the user B decides to update the data in device B and presses the Sync Button (I have the following questions regarding this situation) :-
Can the method mergeChangesFromContextDidSaveNotification insert those two records after the Sync button is pressed ? (I think it can. However, just want to confirm before proceeding ahead with this model)
Is it possible to access the properties of those two objects before inserting them into the database of device B ?

I do not think that iCloud is designed for your use case. iCloud syncs data across devices for the same user i.e. one that is logged in with the same Apple ID in more than one device. The idea is that when the user opens the app on a different device, he can continue working with the data he created / modified on another device before.
There is quite a bit of latency in the updates, so even if you enable / disable syncing via a "Sync" button the actual delivery time is not guaranteed and can take pretty long in some cases depending on connection speeds, chance, the state of Apple servers, etc.
Instead, you could devise a customised solution based on your own server and data scheme.

Since you're using iCloud with Core Data-- no, you can't do anything like this. When iCloud syncs Core Data changes, it does so in the background. It downloads the changes, saves them to the data store, and then, after it's done it tells you about the change that has already happened. Calling mergeChangesFromContextDidSaveNotification does not save the changes-- they're already saved. What that call does is update your managed object context with new data from the persistent store. You get notified of new insertions after they have already happened. Think of the "did import" notification as if it were a "did save" notification from another thread, telling you about something that's already finished.
Now, if you have already loaded a managed object when you receive the import notification, you could compare its current properties to those from the most recent incoming change update. If you don't already have the object in memory, you can't get its old values, because they've already been overwritten.

Related

Why we need to use NSPersistentHistoryTransaction if NSFetchedResultController able to update UI correctly?

I still fail to understand, what is the problem NSPersistentHistoryTransaction is trying to solve, in the CoreDataCloudKitDemo WWDC 2019 "Using Core Data with CloudKit"
https://github.com/software123inc/CoreDataCloudKitDemo/blob/master/CoreDataCloudKitDemo/DataProvider/CoreDataStack.swift#L161
I want to see, what problem will occur, if processPersistentHistory is not executed.
By making the processPersistentHistory empty, I try to do the following testing.
Run 2 simulators simultaneously in the same machine.
Add an item to simulator A.
Since, there is no way for simulator B to receive push notification, I press the home button for simulator B.
In simulator B, I tap on the app icon to launch the app again.
In simulator B, I can observe controllerDidChangeContent is being called. My guess is that, because the backed SQLite is seamlessly updated by CloudKit background task, NSFetchedResultController will be notified the SQLite DB change, and subsequently update the UI. Check the "Download CloudKit Changes into Core Data" of https://developer.apple.com/documentation/coredata/mirroring_a_core_data_store_with_cloudkit/syncing_a_core_data_store_with_cloudkit
In simulator B, due to controllerDidChangeContent is being triggered correctly, I can observe the UI change perform by NSFetchResultController without issue.
Hence, I am not clear, on what problem processPersistentHistory is trying to solve in the demo code. May I know what kind of test case I can perform, to understand the problem solved by processPersistentHistory?
Based on "Integrate Store Changes Relevant to the Current View"
https://developer.apple.com/documentation/coredata/mirroring_a_core_data_store_with_cloudkit/syncing_a_core_data_store_with_cloudkit
Your app receives remote change notifications when the local store
updates from CloudKit. However, it’s unnecessary to update your UI in
response to every notification, because some changes may not be
relevant to the current view.
Analyze the persistent history to determine whether the changes are
relevant to the current view before consuming them in the user
interface. Inspect the details of each transaction, such as the entity
name, its updated properties, and the type of change, to decide
whether to act.
For more information about persistent history tracking, see Consuming
Relevant Store Changes.
This part is getting confusing. Our NSFetchedResultController is receiving relevant entity change event due to SQLite, and subsequently able to update the UI correct. If that is so, why do we still need persistent history?
You are right that NSFetchedResultsController are able to receive updates, and trigger updates to the UI.
So the question becomes, in which UI of your app that you display data from CoreData, and do not use NSFetchedResultsController?
For mine, it is a form editing an item, which I just pass one of item from the NSFetchedResultsController into it as a form member variable. This Form is not able to receive such updates.
Looking at the CoreDataCloudKitDemo, there's also this DetailView, that is letting user edit a single post, and whenever this post is deleted from another device, having persistent history tracking (and query generation) allow it to "trap" this change and show a pop up before closing the edit form.

How to wait until cloud kit data is being synced with core data in swift in iOS13

I developed core data based app and implemented iCloud sync feature after it was introduced in iOS 13.
I enabled iCloud kit, used NSPersistentCloudKitContainer instead of NSPersistentContainer and added several lines of code to sync core data with iCloud.
Sync works fine. The problem is that when I uninstall app and reinstall app, it doesn't fetch iCloud data at first time.
I have to restart app or open another screens to let Core Data to be synced with iCloud.
Is there anyway I can check if core data is being synced with iCloud or wait until it finishes syncing?
Thanks.
There is no API exposed to get the completion handler on sync of all
CKRecord's to NSManagedObject's.
Also the update is working using a background silent notification so we can expect any change anytime. So its good to not having completion handler
What's next
You can create one attribute to check the sync time, just like a date when last sync happened to local record and check the lastModificationDate (By default CKRecord has this property) of the same record in iCloud.
So everytime when you launch the app check for all the records manually if any update is required, then wait for notification of update and keep on checking the sync status
Note: Be careful this may lead to delay your launch process if from any other device user is very active and new records are getting inserted or modified.
Currently, there is no API provided for iCloud sync completion. What you can do is
You can initialize NSPersistentCloudKitContainer and call it's loadPersistentStores() at the start of the app. This will buy you enough time and your data will be available for your internal screens.
You can also listen for remote changes in your local store and update your UI accordingly as mentioned in this Apple doc: https://developer.apple.com/documentation/coredata/consuming_relevant_store_changes
I have to restart app or open another screens to let Core Data to be
synced with iCloud.
To solve this I'm using a notification observer for NSManagedObjectContextObjectsDidChange event.
When reinstalled app is first opened, it shows empty screen and after some time, when data is synced, the notification is triggered and the page is reloaded with the data.
However I also couldn't find any way to wait and show loading message while data is being synced.

How to identify EKEvent uniquely with Sync across the devices

I am trying to make Event Syncing feature for a Project. I need to sync events with the remote server.
Let's say I Installed the App in Device A.
If I login to another device lets take B, then events synced from A should also appear in Device B, and events of B should also be synced.
Now if I again login into the Device A, Events of B should be added.But events previously from A should not again be added to Device A again For this I decided to keep its eventIdentifier on the remote database.
The Issue now happens when I again Go back to Device B, where events previously synced from Device A are already there, so those events should not be added again. But as eventIdentifiers were as per Device A, there is no way to Identify if the event is already added in Device B or not.
Can anyone suggest me the way to achieve such cross-platform event syncing without duplication of the event?
EKEvent has property eventIdentifier, but it's only readOnly property and I can't keep event's remote ID somewhere.
eventIdentifier is not shared between devices - same event will most likely have different eventIdentifier on different devices.
Instead, you need to use the calendarItemExternalIdentifier - this will (generally) be the same on different devices.
One (relatively rare) exception is when an event has been created recently using EventKit, and the event was not yet synced to the server. This causes the calndarItemExternalIdentifier to change at a later time.
Using calendarItemExternalIdentifier you can check if an event was already added by querying to see if there's already a record with the same calendarItemExternalIdentifier in your database. The exception described above needs to be handled separately.

Manage iPhone app both offline and online

I am stuck to a point, I am managing my app offline also.. first time i get all data (images) from a webservices and store its path into sqlite, now I take an int value 0 or 1 and store its state into NSUserDefaults now I am facing problem in updating those images, like if images are changed how should I notify into my app, I searched it on Google and only solution is to send push notification to app when record is updated. What if user do not allow push notification?
Is there any other solution to manage app offline and update only when record is changed from online database?
You can sync your Data with server Periodically , like call a webservice which check if images have been changed then fetch the new images and if images are not changed then continue with old data.You can add a Boolean Value on server end which can tell you if data on server is updated or not or you can check this with Time stamp like when data is updated last time then compare your local time with Server time at which data has been updated . Good Luck !!
There are ways to handle such kind of situation:
Use push notifications which is the best solution for such situation.
Query the server periodically by giving the ability to the user to set the time interval from app settings.
Query the server whenever the app came from background to foreground.
An idea is to use BackgroundFetch to update the app content while it is not running. I am using similar fetch for one of my apps where I update the content before the user opens the app. I am fetching a small list of items that is indicating which one is up to date and which is not. Then, at runtime I present an option to the user to manually update these items, but of course you can also download all that while in backgroundfetch. My app is without network reach most of the time, and backgroundFetch will update the items list first thing when there is some internet connection.
There are two important methods here that you need to register, setMinimumBackgroundFetchInterval: and application:performFetchWithCompletionHandler: . You can read more about them in Apples guides for background modes.
You can also check out this good tutorial about this fetching capability: http://www.appcoda.com/ios7-background-fetch-programming/
There is way which has being most popular as most of peoples are preferring
At server side build mechanism, which can send lists of images updated after particular date/time.
To keep track of last sync date/time at locally.
Pinged on server with last date/time in case of first time pass any past date and check for update, if there is then download updated images, Otherwise.
Now you have to decide this process when would be query to server
1) At every first time application will be load. (At didFinishLaunchingWithOptions).
2) Query the server periodically by giving the ability to the user to set the time interval from app settings or else where would be preferable.
3) Query the server whenever the app came from background to foreground.

iOS Save State when device turns off or battery drains

I have an app that syncs records created on the phone with a web-server.
When app does not have internet connectivity, it store everything locally (Core Data) and then syncs it when internet is available again.
Now a user has come up with a valid user case scenario ... say he is in an airplane making records (without network) ... so everything is getting stored locally. All is well till the air hostess commands him to switch off the phone. While in the app he immediately turns if off. Now the data is getting lost. I am saving sate while app goes in background or even when phone is locked. But how do I warrant for this?
When user switches off the phone (long-pressing lock button), they still have to slide a bar and devices takes some time before switching off ... so there is some time I can save state in ... which delegate method is called during this time?
Your app will get applicationWillResignActive, applicationDidEnterBackground, applicationWillTerminate calls, in that order.
http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html#//apple_ref/doc/uid/TP40007072-CH4-SW3
You can use applicationWillTerminate as a cue to save stuff.
However it is standard good practice to make sure your data is saved periodically as you go along and not just in these situations - what about if your user has entered lots of lots and data and then your app crashes, the data will be lost.
I'm not very familiar with Core Data but surely it must have atomic save ability.

Resources