Can someone point me in the right direction to find out how to correctly monitor when files change in the app iCloud container? I've based my code on Apple and other iCloud tutorials I've reviewed, but none of them deal with updates to the iCloud container, just using initial queries. I've been working on this for three weeks now with no success. I use UIDocument in an app saving to the app iCloud container. Since UIDocument sends no notice when a document is added, I can't update the app on another iOS device when the app is running on multiple devices. Changing and deleting a document works fine by monitoring the UIDocument UIDocumentStateChangedNotification.
I use a query to initially check the iCloud container when the app starts or resumes from the background which works fine to get all files in the iCloud container on the device, including any documents added while the app was not active. I disable updates to process the query results when NSMetadataQueryDidFinishGatheringNotification is posted, then enable updates on the query. Sometimes I get one or two update notices shortly after enabling updates from NSMetadataQueryDidUpdateNotification being posted, but that is all. Never any further update notices and never from a document being added to the iCloud container.
I understand the code for iCloud use is somewhat complex, I don't expect anyone to examine my code (I've provided an excerpt for reference) to correct it. I'd appreciate it if someone can point me to more information on the specifics of tracking iCloud container changes during app execution.
Thanks,
Fred
Code excerpt for starting query:
-(void)loadDocument {
// set iCloud URL to nil for local storage to start
NSURL *ubiq = nil;
// if iCloud is selected get the iCloud container URL
if ([_useiCloud isEqualToString:@"YES"]) {
// get the app iCloud container URL
ubiq = DefaultMemoDataController.iCloudContainerURL;
}
// if iCloud URL is available and user chooses to use iCloud, set the query for app memo file names
if (ubiq) {
// adding to see if not creating another query prevents crash resuming from background
if (!self.query) {
self.query = [[NSMetadataQuery alloc] init];
}
// set the scope of the query to look in iCloud documents
[self.query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
// set search to look for a group of file names by setting up a predicate
// use the note file name format for the app
NSPredicate *pred = [NSPredicate predicateWithFormat:@"%K like 'FOLMemo_*'", NSMetadataItemFSNameKey];
// set the query to search with the predicate.
[self.query setPredicate:pred];
// set up a notification when the query is complete because the query is an asynchronous call (off the main queue)
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(queryDidFinishGathering:)
name:NSMetadataQueryDidFinishGatheringNotification
object:self.query];
// start the query.
[self.query startQuery];
// not sure this is needed, but want to make sure same query is started again for updates.
DefaultMemoDataController.query = self.query;
}
}
code when query completes
-(void)queryDidFinishGathering:(NSNotification *)notification {
// stop the query while processing the query results to prevent changes while processing
NSMetadataQuery *query = [notification object];
[query disableUpdates];
// not sure is needed but want to make sure resume updates on same query
DefaultMemoDataController.query = query;
// stop looking for query did finish notifications since the query was completed.
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSMetadataQueryDidFinishGatheringNotification
object:query];
// start looking for query updates
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(processQueryUpdate:)
name:NSMetadataQueryDidUpdateNotification
object:query];
// load the data from the query
[self loadData:query];
}
code to process query:
-(void)loadData:(NSMetadataQuery *)query {
// add all the memos from the query results to the app memos dictionary
for (NSMetadataItem *item in [query results]) {
// get the URL for the memo
NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
// load the memo text from the url
FOLMemoDoc *doc = [[FOLMemoDoc alloc] initWithFileURL:url];
// open the note
[doc openWithCompletionHandler:^(BOOL success) {
if (success) {
// add the memo UIDocument object to the memo dictionary
// need temp dictionary since can't change a property dictionary for some reason
NSMutableDictionary * tempDict = [NSMutableDictionary dictionaryWithDictionary:DefaultMemoDataController.masterMemoDictionary];
[tempDict setObject:doc forKey:doc.memoDictionaryKey];
DefaultMemoDataController.masterMemoDictionary = [NSMutableDictionary dictionaryWithDictionary:tempDict];
NSNotification *notice = [NSNotification notificationWithName:kFlashofLightUpdateMemoNotice
object:doc];
[[NSNotificationCenter defaultCenter] postNotification:notice];
} else {
// failed to open document, should probably alert the user
}
}];
}
// enable query updates
[query enableUpdates];
}
After another week's experimentation I got query updates for the iCloud container working by adding a property to my persistent dataController object for the query object. By replacing each query reference in my previous code with the persistent dataController property, keeping the observer for the finished query (NSMetadataQueryDidFinishGatheringNotification) and never stopping the query, query updates now works (NSMetadataQueryDidUpdateNotification). The app receives a NSMetadataQueryDidUpdateNotification notification for every change to the app iCloud container. Multiple notices are received at times, but I have not come across a time when a notice is not posted, so I can now catch all real time updates on all devices running the app.
Here are the revised code extracts from above. This code requires other methods and set-up that is not included, so it will not run stand alone, but shows the changes I had to make to get NSMetadataQueryDidUpdateNotification notifications working in my app.
Code excerpt for starting query:
-(void)loadDocument {
// set iCloud URL to nil for local storage to start
NSURL *ubiq = nil;
// if iCloud is selected get the iCloud container URL
if ([_useiCloud isEqualToString:@"YES"]) {
// get the app iCloud container URL
ubiq = DefaultMemoDataController.iCloudContainerURL;
}
// if iCloud URL is available and user chooses to use iCloud, set the query for app memo file names
if (ubiq) {
// adding to see if not creating another query prevents crash resuming from background
if (!DefaultMemoDataController.query) {
DefaultMemoDataController.query = [[NSMetadataQuery alloc] init];
}
// set the scope of the query to look in iCloud documents
[DefaultMemoDataController.query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
// set search to look for a group of file names by setting up a predicate
// use the note file name format for the app
NSPredicate *pred = [NSPredicate predicateWithFormat:@"%K like 'FOLMemo_*'", NSMetadataItemFSNameKey];
// set the query to search with the predicate.
[DefaultMemoDataController.query setPredicate:pred];
//remove observer to make sure no duplicate observers
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSMetadataQueryDidFinishGatheringNotification
object:DefaultMemoDataController.query];
// set up a notification when the query is complete because the query is an asynchronous call (off the main queue)
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(queryDidFinishGathering:)
name:NSMetadataQueryDidFinishGatheringNotification
object:DefaultMemoDataController.query];
// remove observer to make sure no duplicate observers
[[NSNotificationCenter defaultCenter] removeObserver:self
name:NSMetadataQueryDidUpdateNotification
object:DefaultMemoDataController.query];
// set observer for query update
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(processQueryUpdate:)
name:NSMetadataQueryDidUpdateNotification
object:DefaultMemoDataController.query];
// start the query.
[DefaultMemoDataController.query startQuery];
}
code when query initially completes:
-(void)queryDidFinishGathering:(NSNotification *)notification {
// disable the query while processing the query results to prevent changes while processing
DefaultMemoDataController.query
NSMetadataQuery *query = [notification object];
[DefaultMemoDataController.query disableUpdates];
// call loadData with the query results
[self loadData:DefaultMemoDataController.query];
}
code to process query
-(void)loadData:(NSMetadataQuery *)query {
// add all the memos from the query results to the app memos dictionary
for (NSMetadataItem *item in [query results]) {
// get the URL for the memo
NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
// load the memo text from the url
FOLMemoDoc *doc = [[FOLMemoDoc alloc] initWithFileURL:url];
// open the memo
[doc openWithCompletionHandler:^(BOOL success) {
if (success) {
// add the memo UIDocument object to the memo dictionary
// need temp dictionary since can't change a property dictionary for some reason
NSMutableDictionary * tempDict = [NSMutableDictionary dictionaryWithDictionary:DefaultMemoDataController.masterMemoDictionary];
[tempDict setObject:doc forKey:doc.memoDictionaryKey];
DefaultMemoDataController.masterMemoDictionary = [NSMutableDictionary dictionaryWithDictionary:tempDict];
// save the memo dictionary
[DefaultMemoDataController saveMemoDictionary];
NSNotification *notice = [NSNotification notificationWithName:kFlashofLightUpdateMemoNotice
object:doc];
[[NSNotificationCenter defaultCenter] postNotification:notice];
} else {
// failed to open document
// if there is a memo dictionary key available, delete the memo from master memo dictionary
if (doc.memoDictionaryKey) {
// delete memo from master memo dictionary
[DefaultMemoDataController.masterMemoDictionary removeObjectForKey:doc.memoDictionaryKey];
}
// get the dictionary key from the file name and try to delete it that way
else {
NSString * filename = [doc.fileURL lastPathComponent];
if (filename) {
[DefaultMemoDataController.masterMemoDictionary removeObjectForKey:filename];
}
}
}
}];
}
// enable query updates
[DefaultMemoDataController.query enableUpdates];
}
I hope this helps someone else.
Fred