How to identify EKEvent uniquely with Sync across the devices - ios

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.

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.

Schedule UILocalNotification based on changes to Core Data

I'm making a simple app with a Today Widget extension that logs events.
The user can tap a button in the app or the related Today Widget to log an event. These events are saved with Core Data any time the button is pressed either place.
Whenever a new event is logged in the app, I run a function called updateLocalNotificationsFromCoreData(). It handles the setup of UILocalNotifications based on the most recent event in Core Data after clearing the appropriate existing notifications.
However, when a new event is logged from the Today Widget, I can't use this function because I need to register the Local Notification with UIApplication.sharedApplication().scheduleLocalNotification(), and UIApplication is not available in the Today Widget extension.
I realize I'll probably need do something unconventional or hacky to get this working, so I'm trying to evaluate possible approaches and come up with a relatively robust solution.
Basically, I want to find a way I can call my
updateLocalNotificationsFromCoreData() function right away any time a new event is logged.
If I can't do it every time an event is logged, an alternative would be to trigger the updateLocalNotificationsFromCoreData() function periodically (somewhat frequently) another way. Here are some solutions I was thinking about using, but I don't like any of them:
Do it in AppDelegate when the app is launched (or another state change)
One approach I'm thinking about is running my updateLocalNotificationsFromCoreData()function in AppDelegate somewhere, like didFinishLaunchingWithOptions.
The downside is that it would require the user to open the app periodically. If the user didn't open it much the notification behavior would be inconsistent. I'd prefer a solution where a user could interact with only the Today Widget and reliably get Local Notifications without ever opening the app.
Sync the events to a server and use Push Notifications
I've thought about syncing the data in Core Data to a server, then setting up Push Notifications to the user's phone based on that.
I don't like this, because I want the user to still be able to get notifications without an Internet connection. It also introduces a lot of extra overhead of syncing the data with a server.
Ping a server, and send a content-available Push Notification
When someone logs an event with the widget, I could ping a server. That server could send back a silent content-available push notification to trigger the app to run updateLocalNotificationsFromCoreData() in the background.
I found a similar question (Scheduling local notification from within a Today extension) where one answer proposes a similar solution. Unlike the previous solution, an Internet connection is not needed to receive the notifications, but an Internet connection would be required to make sure the notifications are up to date when a new event is logged.
Background fetch
I thought about using Background Fetch to fetch something arbitrary from a server, then run the updateLocalNotificationsFromCoreData(). This would be a way to trigger the update in the background, although it seems silly to fetch data if that data isn't being used, and seems like something for which an app could be rejected. There also seems to be a risk of the system not calling the background update regularly if the user doesn't open the app much and mostly uses the Today Widget.
Use background location updates
This seems like the dumbest approach, but I thought I would mention it anyway since I thought about it. I could use one of the low accuracy background location update modes to trigger updateLocalNotificationsFromCoreData().
It would require the user to allow location in the background, which would be hard to explain. And, it would require the user to at least move around a few blocks to trigger the function, which could provide an inconsistent user experience. Also, it would increase power consumption of the app for a silly reason.
I'd really appreciate fresh ideas about how I might be able to reliably schedule local notifications when Core Data changes on a device that doesn't have an Internet connection!
Or, if that doesn't seem possible, I'd appreciated feedback on which approach seems to make the most sense.
EDIT: I came up with a new solution. It's not ideal, but I think it's better than these other approaches I was considering. When someone taps the button to log the event, I launch the full app. It's annoying because I have all the data I need at that point to give the user feedback and log the event within the Today Widget without launching the app, but by launching the app I have the opportunity to check and schedule local notifications.
Also, in iOS 9 the annoyance on the user is slightly minimized because the system-wide "back" button will appear and let the user go back to the previous app easily once my app has launched from the Today Widget.
In the future I may try a solution where one of the server-based approaches above is used when an Internet connection is available, and I would then fall back to this system of opening the app only when the network connection is not available and I need to schedule the local notifications within the app.

CloudKit: Using notifications to keep changes in sync across multiple devices

I am writing an app and using CloudKit. The app stores its data in the public database. I’ve created a local cache so that my app can function even if the network isn’t available. I’m using CKSubscriptions and the resulting push notifications to keep changes from the cloud in sync. All of this is working well.
Now, if a user has multiple devices and is running my app on all those devices, then I can no longer mark notifications as “read” (using CKMarkNotificationsReadOperation) since I won’t know when all the devices have processed them. This is particularly true if one of the devices is offline when a change happens. If I do mark them as read, then when the other devices check for new notifications (using CKFetchNotificationChangesOperation) they will not see them and their local cache will be out of date.
My current solution is to just leave all notifications in an “unread” state and rely on the CKServerChangeToken in CKFetchNotificationChangesOperation so that each device only grabs the notifications that have occurred since that device last checked. This works well.
But, it seems to me that since I’m not marking any notifications as “read” they will simply continue to pile up on the server. Maybe this isn’t a big deal, but they will take up space and I have no good way to get rid of them. Over time, this seems as if it could be a problem.
Has anyone used subscriptions/notifications in a similar way and come up with a different approach? Also, any feedback on my approach would be welcomed.
The server will delete the old notifications, regardless of the read status.

icloud core data sync

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.

Resources