WatchOS storing and sharing data - ios

I have a Core Data app. It is like a let's say a News app. Each entry has name,id,date,publisher,detail etc. Main iOS app can have lots of News entries. I only want to show let's say first 3 news with the WatchOS app. Since getting news entries needs keyboard usage, I can't initiate transfers from the Watch side. What is the good strategy to share the data? I have thought following scenarios
Send Core Data files with WatchConnectivity transferFile
PROS: Easy
Huge amount of unnecessary data, may not have latest data if the changes aren't saved to context yet.
Whenever News is added send with WatchConnectivity before saving to CoreData.
PROS: Always same data,
CONS: Huge amount of unnecessary data, extra operations to save to new database
When the data is saved to Core Data, query last three objects and send them.
PROS: Small amount of data,
CONS: Need to convert NSManagedObject to another object first, may send same data
Could you help me to find a better way to sync iOS app with WatchOS app? Thanks.

I think the best approach would be background transfers using the application context. That has the following advantages:
You don't have to care about whether your watch app is running or not. When you add your data to the application context it gets added to to a transfer queue and whenever the watch app gets active, it receives the data.
Every time you add your three items, the old items get overwritten. So you always have only 3 items in the queue. That's ideal for a news app, where you don't want to bother your user with old news. So sending the same data several times is no problem, only the latest data "survives".
The only downside would be that you have to serialize your NSManagedObject. I don't know how complex your objects are, but if they are you could use library like HyperSync or Groot
So this is how you would then sync your phone with your app:
1. Set up the session:
if WCSession.isSupported() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
Do this on both places: In your main app and also in the watch extension. If you are only sending data from your main app to the watch you do not need to set the delegate on the main app side.
2. Implement the delegate method:
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
// deserialize the received data,
// store it in CoreData on your watch
// and update the UI
}
3. Send the data:
let dataDict = latestThreeNewsObjects.serializeToDictionary() // However you achieve this ;-)
do {
try WCSession.defaultSession().updateApplicationContext(dataDict as! [String : AnyObject])
} catch {
print("Cannot send data to watch: \(error)")
}
So then, every time you add new news items to your main app CoreData, fetch the latest three NSManagedObjects, serialize them into a dictionary and update your application context. This way the watch always has the latest 3 news when it gets active. When it already is active, the news get updated immediately.
One more thing: Before trying to send data to the watch, you should always check if the user has installed the app on his watch. WCSession has a property for that: watchAppInstalled. If the app is not installed, don't waste resources sending data into the abyss...

Related

NSUbiquitousKeyValueStore taking too long for initial sync

I want to use NSUbiquitousKeyValueStore to store in iCloud some simple key-value pairs for a game I am making. I was under the impression that if the user deleted and then reinstalled the game, their progress would be restored when the app launched.
This appears not to be the case. From the testing I have done, the key-value pairs take a long time to get downloaded from iCloud upon the first launch of the app. After that, the data seems to get uploaded and downloaded almost instantly. This causes issues for my app because when it is reinstalled, it does not immediately have the users previous data and creates a new set of data, negating the point of using NSUbiquitousKeyValueStore.
Is there a way to ensure that information from NSUbiquitousKeyValueStore is available as soon as possible after the app is first launched, and if not what other iCloud APIs could I use?
To ensure that information from NSUbiquitousKeyValueStore is available as soon as possible after the app is first launched. You need to do two steps.
Step 1:- Register for the
NSUbiquitousKeyValueStoreDidChangeExternallyNotification
notification during app launch.
Step 2:- Call the Instance Method
NSUbiquitousKeyValueStore.default.synchronize() The recommended
time to call this method is upon app launch, or upon returning to the
foreground.
For Example:-
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(ubiquitousKeyValueStoreDidChange), name: NSUbiquitousKeyValueStore.didChangeExternallyNotification, object: NSUbiquitousKeyValueStore.default)
NSUbiquitousKeyValueStore.default.synchronize()
// referesh and retrieve keys
}
#objc func ubiquitousKeyValueStoreDidChange(notification:Notification) {
// Get the reason for keys changed
let changeReason = notification.userInfo![NSUbiquitousKeyValueStoreChangeReasonKey] as! Int
// get keys changed.
let changeKeys = notification.userInfo![NSUbiquitousKeyValueStoreChangedKeysKey] as! [String]
switch changeReason {
case NSUbiquitousKeyValueStoreInitialSyncChange, NSUbiquitousKeyValueStoreServerChange, NSUbiquitousKeyValueStoreAccountChange:
// referesh and retrieve keys
case NSUbiquitousKeyValueStoreQuotaViolationChange:
// Reduce Data Stored
}
}
NB: All types of iCloud storage, for example, NSUbiquitousKeyValueStore offer only eventual consistency and thus does not support instant multiple device access.

Communication between watch app and parent iphone app

Requirement :My Watch app will show latest data from our server.
I tried :
To implement this thing I used
WKInterfaceController.openParentApplication(requestDict, reply: { (returnedObject, error) -> Void in
if (returnedObject != nil) {
//loading interface data here
}
})
In my app delegate function I used
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply {
// doing web service call using asynchronous nsurlconnection and replying with response dictionary
}
Problem :
Problem is that application is running fine when iPhone application is foreground but watch app not showing anything when iPhone application is running in background. I debugged it and found actually when iPhone application is running background then webservice api call(nsurlconnection) is not retuuning any data, when it's coming to foreground then it's replying data to watch app.
To solve it I used nsuserdafults to store data, but problem is that it's not always showing latest data. Let consider user opened watch app and it will go to parent application and returning old data from userdafults.
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply {
if ([userInfo[#"type"] isEqualToString:#"list"]) {
[self fetchWatchData];//it will get and store data when app will be foreground
NSDictionary *replyDict = [UtiltiyManager getWatchData];//data from userdefaults
if (replyDict) {
reply(replyDict);
}else {
NSDictionary *noDataDict = #{#"data":#"nodata"};
reply(noDataDict);
}
}
}
Problem is that watch app can't get latest data from iphone while it's in background. As there are no service call api which will work in background. I checked with NSURLConnection and NSURLSessionDataTask both are foreground api call.
Any solutions or thoughts?
Update 1 :
Apple Docs Says :
Types of Tasks Within a session, the NSURLSession class supports three
types of tasks: data tasks, download tasks, and upload tasks.
Data tasks send and receive data using NSData objects. Data tasks are
intended for short, often interactive requests from your app to a
server. Data tasks can return data to your app one piece at a time
after each piece of data is received, or all at once through a
completion handler. Because data tasks do not store the data to a
file, they are not supported in background sessions. Download tasks
retrieve data in the form of a file, and support background downloads
while the app is not running. Upload tasks send data (usually in the
form of a file), and support background uploads while the app is not
running.
Apple told data tasks is not available in background.And my data is small web service data that can be fetched using data task. so my service call is not download task. So in case of watch when iPhone app is background then how app will get web service data.
Should we use download task? But I guess it's intended to download any file.
You need to create a background task in the iPhone app otherwise the OS kills your app off before it can finish downloading the data. Here's some docs to help: https://developer.apple.com/library/ios/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/BackgroundExecution/BackgroundExecution.html
Things have substantially changed since the question and answer posted previously. openParentApplication is no longer available in WatchOS 2. On the plus side, much more can now be achieved directly on the watch to update data from the server as required. Ideally, the iPhone app would also be caching for the WatchKit App Extension with the last cached data in the meantime via one of the new communication mechanisms now available, so that the WatchKit App has something to display until the latest data is downloaded, even if the iPhone app is not currently running.

Ensuring 'Today' extension has updated information from Core Data

I have an application and a today extension that are sharing a Core Data persistent store using a security group identifier.
The main app updates the Core Data store and the extension only reads from the store.
I am using a NSFetchedResultsController in both the app and the extension to retrieve the objects.
This basically works, except that when the main app adds new objects to the store they aren't visible to the extension immediately. Similarly the extension can still see objects after they have been deleted by the main app.
Terminating the app (swiping up from the task switcher) causes the extension to see the changed data immediately.
How can I ensure that the extension always sees the update?
I found the solution was to remove the cached data from the NSFetchedResultsController in the today extension. I added the following line to my code that set up the NSFetchedResultsController
[NSFetchedResultsController deleteCacheWithName:self.fetchedResultsController.cacheName];

iCloud + Core Data: First import and user's feeling of loss of data

I've implemented an iPhone application that has around 50k users. Switching from iOS7 to iOS8 a lot of these users have experienced a terrible feeling when they thought that they data get lost.
I've implemented the first-import behaviour that I thought was the one suggested by Apple
1) Users launch the App
2) iCloud, automatically, starts synching data previously stored on iCloud
3) At some point user get notified that data from iCloud is ready thanks to NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted
The problem is with 3) At some point:
Users that have to sync a lot of data need minutes to get the synch completed and in the meanwhile they think that their data is lost.
I really don't know how to let my users know that they have to wait to see their data synched, because I don't know when this operation starts.
I'm thinking about a possible solution:
During the first launch of the App, asking to the user if he wants to use iCloud. If he chooses to use it, building the database with iCloud options, so I know exactly that the synch is starting here (I suppose...)
I'm really not sure about how to implement this behaviour since I've always seen Core Data settings into the AppDelegate but to achieve this behaviour I suppose I need to move all the CoreData settings in a Controller.
What do you think about this solution? how are you working around this problem in you Apps?
Your idea is right, at least it is that what we do. But leave it in the appDelegate.
Differentiate between with iCloud and without iCloud when doing the "addPersistentStoreWithType". If you do it with iCloud options, it will directly start to build the local store which is a kind of a placeholder ( I'm sure you know that, but just to make my thoughts clear). As soon as this is done, the sync starts from iCloud. So this is the starting point I understood you were looking for.
You can watch that process using the notifications by NSPersistentStoreCoordinatorStoresDidChangeNotification and inform you user accordingly triggered by that notification.
If you look at "Reacting to iCloud Events" in the docs https://developer.apple.com/library/ios/documentation/DataManagement/Conceptual/UsingCoreDataWithiCloudPG/UsingSQLiteStoragewithiCloud/UsingSQLiteStoragewithiCloud.html#//apple_ref/doc/uid/TP40013491-CH3-SW5 there is a detailed desc.
To summarize, the event you're describing is part of the account transitions process. An account transition occurs when one of the following four events is triggered:
Initial import
the iCloud account used did change
iCloud is disabled
your application's data is deleted
During this event, Core Data will post the NSPersistentStoreCoordinatorStoresWillChangeNotification and NSPersistentStoreCoordinatorStoresDidChangeNotification notifications to let you know that an account transition is happening. The transition type we're interested in is NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted.
For information, I've moved all Core Data related code to my own Manager for simplicity and use it with a singleton design pattern. While setting up the singleton, I register the Manager for all relevant notifications (NSPersistentStoreDidImportUbiquitousContentChangesNotification, NSPersistentStoreCoordinatorStoresWillChangeNotification, NSPersistentStoreCoordinatorStoresDidChangeNotification, NSPersistentStoreCoordinatorWillRemoveStoreNotification).
I store several informations in my settings (NSUserDefaults or anything) like the last iCloud state (enabled, disabled, unknown), if the initial import is done or not, etc.
What I end up doing was having a prompt (UIAlertController or anything) to get a confirmation if the user wants to use iCloud or not. I have a displayICloudDialogAndForce:completion: method to do that and only do that if my iCloud state setting is unknown or I use the force parameter.
Then, after the user input, I call a setupCoreDataWithICloud: method, the iCloud boolean parameter depending on the user choice. I would then setup my Core Data stack, on the cloud or not according to the iCloud parameter.
If I'm setting up using iCloud, I would check my settings for the value of an iCloud imported key (boolean). If the value is NO, then I'm presenting a new modal to warn the user about the incoming import that could take some time.
I've registered my manager for different notifications and specially NSPersistentStoreCoordinatorStoresDidChangeNotification. In my storeDidChange: callback, I'm checking the transition type and if it's NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted, I'm changing the content of my modal to show the user that the import was successful and removing it a few seconds later, saving in my settings that the initial import is done.
- (void)storeDidChange:(NSNotification *)notification
{
NSPersistentStoreUbiquitousTransitionType transitionType = [notification.userInfo[NSPersistentStoreUbiquitousTransitionTypeKey] integerValue];
if (transitionType == NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted) {
[settings setDefaults:#(YES) forKey:kSettingsICloudImportedKey];
[ICloudModal dismissWithSuccess];
// ...
}
// Do other relevant things...
}

Definitive Way to Save Data Between Launches.

For a project I'm working on, I would like to save a few bytes of data to the users iPhone between launches of the application. I would like to do this so I can save some state and a few important numbers when the user terminates the app. I have thought about it, and the best place to do this seems like the AppDelegate.
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate.
// Save data if appropriate. See also applicationDidEnterBackground:.
/* this is where I want to begin the process of saving application data */
}
I have heard of writing objects (only a select few allowed such as NSString, NSDictionary, NSArray) to file, but can this be kept around between launches of the app, or does the data go away when the user terminates the app?
Question:
Is there a definitive way to write data to the users iPhone that will stick around after the application quits?
The most common approach is to persist whatever data or state you need when your app enters the background. Then the data will be there if the app is killed while in the background and the app is restarted.
How you store the data really depends on what the data is. Writing the data to a file in the app's sandbox is quite common. Small bits of data can also be stored in NSUserDefaults.

Resources