I am working on Core Data syncing over iCloud between two Mac applications. I have it mostly working (based on http://timroadley.com/2012/04/03/core-data-in-icloud/) - data is being synced, but I'm never getting the content change notification I need to merge the changes into my local context.
In my AppDelegate's managedObjectContext method:
NSManagedObjectContext * moc = [[NSManagedObjectContext alloc]
initWithConcurrencyType:NSMainQueueConcurrencyType];
[moc performBlockAndWait:
^{
[moc setPersistentStoreCoordinator:coordinator];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(mergeChangesFrom_iCloud:)
name:NSPersistentStoreDidImportUbiquitousContentChangesNotification
object:coordinator];
}];
My observer function never gets called:
- (void) mergeChangesFrom_iCloud:(NSNotification*) notification
{
NSLog(#"Merging changes from iCloud");
// everything past here is irrelevent - this log message never appears
The data store is most certainly being synced - deleting a record in app A and forcing a table reload in app B results in a Core Data fault failure as expected for unmerged changes. Quitting and restarting app B shows the correctly updated data.
A breakpoint on the addObserver call shows it is being called. A breakpoint in mergeChangesFrom_iCloud shows that it is not.
Why is the coordinator apparently never sending the NSPersistentStoreDidImportUbiquitousContentChangesNotification as the documentation (and tutorials) claim it should? Where can I begin debugging this?
Update
The mystery deepens. Using the Core Foundation function shown here - NSNotificationCenter trapping and tracing all NSNotifications - I do not see NSPersistentStoreDidImportUbiquitousContentChangesNotification appear at all among the mass of notifications flying by.
It looks like it's not being posted at all - nor do I see any other Core Data related notifications after the initial load happens on startup.
Related
While Parse Cloud Code provides an on-save hook that lets you perform custom actions on the backend when objects are saved, their iOS SDK doesn't have any similar hook for when objects are saved into the local datastore with -save(Eventually)* methods.
I would like changes in data to drive my custom actions, such as update the application's UI, refreshing a table etc.. How could this be done? Are there any NSNotifications that you could observe?
A lot would depend on if your local "afterSave" is dependent on confirmation of a save in the cloud or not. Consider the following two examples.
You can pinInBackgroundWithBlock and send a notification as well as save eventually. The notification would send right away unless there was an error pinning. Keep in mind that your saveEventually could still fail.
[myObject pinInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if( succeeded ){
[[NSNotificationCenter defaultCenter]postNotificationName:#"localBeforeSave" object:myObject];
[myObject saveEventually];
}
}];
Or you can just use a block to send a local notification once you can confirm that the save eventually completed.
[myObject saveEventually:^(BOOL succeeded, NSError *error) {
[[NSNotificationCenter defaultCenter]postNotificationName:#"localAfterSave" object:myObject];
}];
I am calling scanForPeripheralsWithServices from didFinishLaunchingWithOptions callback of AppDelegate. The code looks like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
NSLog(#"didFinishLaunchingWithOptions");
// Override point for customization after application launch.
cm = [[CBCentralManager alloc] initWithDelegate:self queue:nil];
[cm scanForPeripheralsWithServices:nil
options:nil];
}
- (void) centralManager:(CBCentralManager*)central didDiscoverPeripheral:(CBPeripheral*)peripheral advertisementData:(NSDictionary*)advertisementData RSSI:(NSNumber*)RSSI{
NSLog(#"Did discover peripheral %#", peripheral.name);
[cm stopScan];
}
Before upgrading to iOS 8 everything was working well, however after the upgrade (exactly the same code, no single line was changed) I am not getting any error, but also didDiscoverPeripheral is not being called.
I appears that the major change which has occurred with Core Bluetooth in iOS 8 is that the BLE stack isn't powered on until you try to connect (or possible issue some other command).
CBCentralManager *cbCentralManager;
[cbCentralManager scanForPeripheralsWithServices:...];
This call used to issue a warning visible in the Xcode debug log saying that the central manager needed to be powered-up before it could be used. However, this warning was always a catch-22 — the only way to power-on the central manager was to send it a message, and the only way for it to process a message was for it to be powered-on.
Apple seems to have resolved this problem by handling power-on a bit differently. Now, after issuing the above command the central manager tells its delegate that its state changed via centralManagerDidUpdateState:.
We resolved the problem you describe by responding to centralManagerDidUpdateState: by re-issuing the scanForPeripherals... message.
I am working on a Mac & iOS App, with iCloud CoreData in between to synchronize the data.
When I update some thing from iOS App, and the updates are already migrated to the PersistentStore in Mac App while the Mac App is running. The problem is I cannot find an effective way to force the NSArrayController to reload all data from the store.
tried -(void) fetch:(id)sender; only can see the delete or added entity, but the updated model property not refreshed...
Please help. Thanks
If you see the latest data in the managed object context, but not in the array controller, you want:
[_yourManagedObjectContext processPendingChanges];
[_yourArrayController fetchWithRequest:nil merge:YES error:&error];
[_yourArrayController rearrangeObjects];
I use this in a Mac/iOS iCloud app to update the Mac app's data when the iCloud store changes.
Following is the reply from Apple's Developer Technical support. It worked for me. Thanks all for providing solutions.
For the OS X app, when reloadFetchResults is called, you can ask the NSManagedObjectContext to reset itself and perform the fetch again.
- (void)reloadFetchedResults:(NSNotification *)note
{
NSDictionary *userInfoDict = [note userInfo];
NSManagedObjectContext *moc = self.coreDataController.mainThreadContext;
// this only works if you used NSMainQueueConcurrencyType
// otherwise use a dispatch_async back to the main thread yourself
//
[moc performBlock:^{
[self mergeiCloudChanges:userInfoDict forContext:moc];
[moc reset];
[self.tableArrayController fetch:self];
}];
}
I've found that
[self.managedObjectContext reset];
[myArrayController fetch:self];
forces my NSTableView (with NSArrayController) to re-populate and display newly processed NSManagedObjects.
in case someone else finds this...
in my case, I was building the array from user interaction (not Core Data), and then trying to show the tableView when they were done (on the same window)... and of course... seeing nothing!
[arrayController rearrangeObjects];
just before I wanted to show the tableView fixed it for me.
In my application, I have registered the NSUserDefaultsDidChangeNotification notification in awakeFromNib along with my other notifications (which are working fine). I register it just as shown below, which appears to be correct:
- (void)awakeFromNib
{
[[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:#selector(myAction:) name:NSUserDefaultsDidChangeNotification object:nil];
}
and my action method appears to be correctly formatted as well:
- (void)myAction:(NSNotifcation*)notifcation
{
NSLog(#"foo);
}
For unknown reasons, however, it refuses to fire at all. No matter how many of the application's defaults are changed, it never fires even once. Am I registering wrong or is something else the cause? Has anybody else experienced this problem in the past?
Have you tried using [NSNotificationCenter defaultCenter] instead of the workspace notification center?
Hmmm……
A question about UILocalNotification and the notificaton's alertLaunchImage.
My app uses UILocalNotifiaction(s) to get users' attention. As usual, an alert is presented with "Action" and "Close" buttons. When the user taps Action, the image specified by alertLaunchImage is presented. The alertLaunchImage is a screenshot of of one of the views of the app which is shown after the data is initialized when launched normally.
Here are the 3 cases when the notification is delivered:
App is running in foreground - no alert, no launchImage is shown as designed. No problems.
If my app is running in background when the notification is delivered, the launchImage works like a charm. No problems. The launchImage with no app-related data is shown and then the app fills up the data. This part works seamlessly.
However, if the app is not running when the notification is delivered, the sequence is confusing - or I missed something. The app gets launched and shows the alertLaunchImage instead of the Default image. Then is goes thru several other screens (as part of initialization and data processing) before the actual screen (live version of alertLaunchImage) is shown.
This can get very confusing to the user. My question comes in here. How can this be avoided?
R/-
Sam.!
you can try cleaning up the alert view settings in applicationWillTerminate:
According to the UIApplicationDelegate reference applicationWillTerminate::
"This method lets your application know
that it is about to be terminated and
purged from memory entirely. You
should use this method to perform any
final clean-up tasks for your
application, such as freeing shared
resources, saving user data,
invalidating timers, and storing
enough application state to
reconstitute your application’s
interface when it is relaunched"
HTH,
Oded
If your app is launched by a local notification, you will receive that notification in the options passed to -application:didFinishLaunchingWithOptions:. Based on that, you can write code that navigates to the correct screen without animations.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UILocalNotification *localNotification = [launchOptions valueForKey:UIApplicationLaunchOptionsLocalNotificationKey];
if (localNotification != nil) {
// startup by local notification
} else {
// normal startup
}
}