iCloud: getting an array of files present in the cloud - iphone

I'd like to get a list of all txt files stored in a user's iCloud account. After checking if iCloud is enabled, I tried the following:
NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
_query = query;
[query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"self ENDSWITH '.txt'"];
[query setPredicate:pred];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query];
[query startQuery];
But I get the following console output and a crash:
2011-10-23 21:58:19.587 iCloudText[9922:707] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'NSComparisonPredicate with left expression which is not NSKeyPathExpressionType given to NSMetadataQuery (SELF ENDSWITH ".txt")'
*** First throw call stack:
(0x30ef58bf 0x3809a1e5 0x30ef57b9 0x30ef57db 0x37f4e725 0x37f4b0b5 0x29b3 0x2c4b 0x32ec77eb 0x32ec13bd 0x32e8f921 0x32e8f3bf 0x32e8ed2d 0x33a7ae13 0x30ec9553 0x30ec94f5 0x30ec8343 0x30e4b4dd 0x30e4b3a5 0x32ec0457 0x32ebd743 0x2437 0x23dc)
terminate called throwing an exception(gdb)
What am I doing wrong when defining the predicate? I've read the docs but can't figure out where I went wrong. Any help would be very much appreciated!

Try using [NSPredicate predicateWithFormat:#"%K ENDSWITH '.txt'", NSMetadataItemFSNameKey] as your predicate. SELF doesn't refer to the filename in this case.

Related

Unrecognized selector error processing results from geocodeAddressString

I'm trying to create multiple placemarks using MKMapItem without using coordinates.
I used location name directly in geocodeAdressString:#"Mumbai"... but I got result for single location.
While I use multiple locations through array, I'm getting this error:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI length]: unrecognized selector sent to instance 0xab48380'
Why is this problem occurring?
Class mapItemClass=[MKMapItem class];
if(mapItemClass &&[mapItemClass respondsToSelector:#selector(openMapsWithItems:launchOptions:)])
{
NSArray *addr=[[NSArray alloc ]initWithObjects:#"Banglore",#"Mumbai",#"Delhi", nil];
CLGeocoder *geocoder=[[CLGeocoder alloc]init];
[geocoder geocodeAddressString:addr completionHandler:^(NSArray *placemarks, NSError *error) {
CLPlacemark *geocodedPlacemark=[placemarks objectAtIndex:0];
MKPlacemark *placemark=[[MKPlacemark alloc]initWithCoordinate:geocodedPlacemark.location.coordinate addressDictionary:geocodedPlacemark.addressDictionary];
MKMapItem *mapItem=[[MKMapItem alloc]initWithPlacemark:placemark];
[mapItem setName:geocodedPlacemark.name];
[MKMapItem openMapsWithItems:#[mapItem] launchOptions:nil];
}];
}
The error state that -[__NSArrayI length]: unrecognized selector sent to instance 0xab48380'
NSArray dont have a property length. So it is unable to find the selector of length. So check where you are using NSArray and keep break points to find where error is happening. length is the method of NSString and NSData but NSArray dont have length, it has count
I have been able to replicate this using this code
id array = [[NSArray alloc] initWithObjects:#"Hello", #"World", nil];
NSLog(#"%d",[array length]);
output
-[__NSArrayI length]: unrecognized selector sent to instance 0x96bafe0
notice how I have used id instead of NSArray with using id I am able to call length which isn't allowed by NSArray, but this gets round the compiler when using id.
So the best way to find out where this is going wrong is add an exception that will catch all exceptions. Do this by selecting the exceptions tab in the project navigator wind >> select '+' >> 'Add Exception Breakpoint...' >> then just select done. This will set a breakpoint every time your app throws an exception.
EDIT
Thanks to the code you have added I suspect that you are passing an NSArray where there should be an NSString. You create
NSArray *addr=[[NSArray alloc ]initWithObjects:#"Banglore",#"Mumbai",#"Delhi", nil];
then pass it to geocodeAddressString:addr
[geocoder geocodeAddressString:addr completionHandler:^(NSArray *placemarks, NSError *error) {
Just from the name of this parameter I suspect it should be an NSString and not an NSArray try replacing addr to [addr objectAtIndex:0] this will get the string object at index 0 of the addr array.
EDIT 2
Here is the method you are calling notice it only allows an NSString to be passed in for geocodeAddressString.
- (void)geocodeAddressString:(NSString *)addressString completionHandler:(CLGeocodeCompletionHandler)completionHandler;

NSOperationQueue operations filteredArrayUsingPredicate error

I have class MyOperation : NSOperation with #property (nonatomic, retain) NSString *oID;
And sometimes I need to cancel operation with specific oID. I'm trying to do this:
NSArray *operations = operationQueue.operations;
NSPredicate *predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat: #"oID == %#", _specificID]];
NSArray *arrayOperations = [operations filteredArrayUsingPredicate: predicate];
and get error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Unable to parse the format string "oID == 0f5db97b-f127-4425-ad79-451d1f204016"'
Is it possible to filter operation from NSOperationQueue?
Your problem is that you are formatting a string, then sending that to the Predicate constructor method. It's already a formatter, and knows how to format the data you give it.
Basically, you need the format to look like:
oID == "0f5db97b-f127-4425-ad79-451d1f204016"
but you are getting
oID == 0f5db97b-f127-4425-ad79-451d1f204016
If you use the formatter by itself, you should get past this issue...
NSPredicate *predicate = [NSPredicate predicateWithFormat: #"oID == %#", _specificID];
NOTE: The predicate formatter knows it should handle strings specially, and automatically adds the extra quotation characters when you pass a string to be formatted by %#'
I'm going to guess that the operations.queue is a mutable array (logging its class returns this '__NSArrayM'). What you should try is:
NSArray *operations = [NSArray arrayWithArray:operationQueue.operations];
That said, some operation may have completed (and been removed from the mutable queue) by the time the predicate is applied to it.

NSMetadataQuery doesn't finish gathering (no notification)

I'm making a backup managrer for my App (via iCloud). I did some tests and the basics worked. But few days later it stopped. I'm using NSMetadataQuery for searching if backup file exists. My backup files are named e.g. Backup29112011154133.xml where numbers represent date of the backup (formatted as ddMMyyyyHHmmss). I check for it in -viewDidAppear:
- (void)viewDidAppear:(BOOL)animated {
[self checkForRemoteFile];
}
- (void)checkForRemoteFile {
NSURL *ubiq = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
if (ubiq) {
NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
[query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"%K like 'Backup*'",NSMetadataItemFSNameKey];
[query setPredicate:pred];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query];
[query startQuery];
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"iCloud is unavailable at the moment" message:nil delegate:self cancelButtonTitle:#"Close" otherButtonTitles:nil];
[alert setTag:TAG_ALERT_NOICLOUD];
[alert show];
}
}
- (void)queryDidFinishGathering:(NSNotification *)notif {
NSMetadataQuery *query = [notif object];
[query disableUpdates];
[query stopQuery];
[self loadRemoteFile:query];
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:query];
}
- (void)loadRemoteFile:(NSMetadataQuery *)query {
if ([query resultCount] == 1) {
canRestore = YES;
NSMetadataItem *item = [query resultAtIndex:0];
// parse the backup file
[self.tableView reloadData];
} else {
canRestore = NO;
modifDate = #"never";
backupInfoLoaded = YES;
[self.tableView reloadData];
}
}
The problem is that - (void)queryDidFinishGathering:(NSNotification *)notif is never executed. I put breakpints and NSLogs ion there but nothing happend.
I also tried to check for other notifications e.g. 'query did start gathering' and 'query process'. Only 'query did start' notification is posted.
I also have AppID with iCloud registered and entitlements file attached.
Can you help me out what's going on? Maybe I missed something?
First of all NSMetadataQuery doesn't works if startQuery was called not from the MaintThread.
There is possibility that predicate fails for every path also.
Following code works for me.
NSURL *mobileDocumentsDirectoryURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
...
query.predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:#"%%K like \"%#*\"", [mobileDocumentsDirectoryURL path]], NSMetadataItemPathKey];
[query startQuery];
FIXED by creating ivar for NSMetadataQuery.
I don't know why the application can't read data without NSMetadataquery ivar.
Unfortunately there have been many problems with iCloud and using NSMetaDataQuery. To be honest with you the best source as of now for all your iCloud related questions is the Apple Developer Forums. Today Apple released iOS 5.1 beta, and the release notes STILL say that NSMetaDataQuery isn't functioning properly. It's extremely frustrating that iCloud still isn't working properly, but sadly there's nothing we can do.
This problem still persists. I have been able to trace it to the following divergence:
If you limit your search predicate on the query to the name key,
for example
[NSPredicate predicateWithFormat:#"%K like[cd] %#", NSMetadataItemFSNameKey, #"*"]
then it will work as expected (posting all four query lifecycle notifications).
If, however, you try either a compound predicate or try to work with the path,
as in
[NSPredicate predicateWithFormat:#"%K BEGINSWITH %#", NSMetadataItemPathKey, [self cloudDocumentsURL].path]
OR
[NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:namePred, pathPred, nil]];
Then only the initial notification will be posted.
I have tried literally hundreds of combinations of these configurable variables in multiple test and intended-for-production apps over the last year and have yet to find a counterexample to this hypothesis.
Unfortunately, NSMetadataQuery just doesn't work for ubiquitous stores (as of 10.8).
My workaround is to get the raw results from the query and work mostly on a bound NSArrayController which can have its results filtered. This will mean refactoring away from query.results for most existing code and there is a performance hit (presumably) but it is the only way I have found. I would love an alternative.

iCloud: How to read in directories created by the user

I would like to read in a list of all directories that are created either by the user or the app in iCloud's Mobile Documents directory (the one found in Lion under ~/Library/Mobile Documents). Here is an example of how this directory could look like:
I tried the following code, but the query I run will not contain any objects representing my folders (using the NSPredicate predicateWithFormat:#"%K.pathExtension = ''", NSMetadataItemFSNameKey). If I run a query for txt files (using #"%K ENDSWITH '.txt'", NSMetadataItemFSNameKey), I will get 5 objects returned for the txt files respectively. Looking for txt files thus works, but not for directories. Reading through the docs, I noticed that Apple suggests to use NSFileWrapper (File Packages) instead of directories. Is iCloud not able to handle/detect directories created by the user or the app?
Here is my code:
-(void)loadDocument {
NSMetadataQuery *query = [[NSMetadataQuery alloc] init];
_query = query;
//Search all files in the Documents directories of the application’s iCloud container directories:
[query setSearchScopes:[NSArray arrayWithObject:NSMetadataQueryUbiquitousDocumentsScope]];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"%K.pathExtension = ''", NSMetadataItemFSNameKey];
[query setPredicate:pred];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(queryDidFinishGathering:) name:NSMetadataQueryDidFinishGatheringNotification object:query];
[query startQuery];
}
- (void)queryDidFinishGathering:(NSNotification *)notification {
NSMetadataQuery *query = [notification object];
[query disableUpdates]; // You should invoke this method before iterating over query results that could change due to live updates.
[query stopQuery]; // You would call this function to stop a query that is generating too many results to be useful but still want to access the available results.
[self loadData:query];
[[NSNotificationCenter defaultCenter] removeObserver:self name:NSMetadataQueryDidFinishGatheringNotification object:query];
_query = nil; // we're done with it
}
- (void)loadData:(NSMetadataQuery *)query {
NSLog(#"Query count %i", [query resultCount]);
for (int i=0; i < [query resultCount]; i++) {
NSMetadataItem *item = [query resultAtIndex:i];
NSURL *url = [item valueForAttribute:NSMetadataItemURLKey];
NSLog(#"%i.URL: %#", i, url);
}
}
I had a look at "Manage Storage" in the iClouds Settings in Mac OS X Lion. When I click my application, it will only show the different txt files (plus conflicting versions) and no directories whatsoever. So I have to assume that you can only work with wrappers / file packages but not with directories.

updating fetched object results in 'unrecognized selector sent to instance' error

Trying to update an fetched object and getting 'unrecognized selector sent to instance' error whenever I try and set any property on the object, here is my code:
NSFetchRequest *request = [[NSFetchRequest alloc]init];
NSEntityDescription *entities = [NSEntityDescription entityForName:#"PurchaseOrderItem" inManagedObjectContext:managedObjectContext];
[request setEntity:entities];
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ItemSKU=%#",collectionItem.ItemSKU];
[request setPredicate:predicate];
NSArray *results= [[managedObjectContext executeFetchRequest:request error:&error] mutableCopy];
if ([results count]==0) {
/* add another purchase order item */
PurchaseOrderItem *newPOItem = (PurchaseOrderItem*) [NSEntityDescription insertNewObjectForEntityForName:#"PurchaseOrderItem" inManagedObjectContext:managedObjectContext];
[newPOItem setProductName:productName.text];
[newPOItem setPrice:price];
[newPOItem setQuantity:qty];
[newPOItem setItemSKU:ItemSKU];
[newPOItem setSubTotal:itemSubTotal];
}else {
/* update item */
PurchaseOrderItem *updateObject = (PurchaseOrderItem*)[results objectAtIndex:0];
[updateObject setSubTotal:itemSubTotal];
[updateObject setQuantity:qty];
}
EDIT:
here is the exact error:
2010-07-12 22:07:37.920 RCoreData[46733:207] *** -[PurchaseOrder setSubTotal:]: unrecognized selector sent to instance 0x587b270
2010-07-12 22:07:37.921 RCoreData[46733:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[PurchaseOrder setSubTotal:]: unrecognized selector sent to instance 0x587b270'
2010-07-12 22:07:37.921 RCoreData[46733:207] Stack: (
46504016,
47661868,
46512731,
45975158,
45971954,
209089,
3382510,
3879998,
3889344,
3884141,
3509736,
3401283,
3432920,
53940604,
45783196,
45779112,
53934237,
53934434,
3425138,
10940,
10794
)
terminate called after throwing an instance of 'NSException'
Note: Adding the item is working great, it is the update part that is throwing the error:
/* update item */
PurchaseOrderItem *updateObject = (PurchaseOrderItem*)[results objectAtIndex:0];
[updateObject setSubTotal:itemSubTotal];
[updateObject setQuantity:qty];
PurchaseOrderItem *newPOItem = (PurchaseOrderItem*) [NSEntityDescription insertNewObjectForEntityForName:#"PurchaseOrderItem" inManagedObjectContext:managedObjectContext];
I think this line having problem. Are u sure that your newPOItem is in the correct class, are you sure that casting works well. Can you try with some code like: [newPOItem isKindOfClass:] and [newPOItem respondsToSelector:]
First, the cast is completely unnecessary. Both the -insertNewObjectForEntityForName:inManagedObjectContext: and -objectAtIndex: return id which is a generic object. The cast does nothing.
Second, when you run this in the debugger, what do you get when you
po 0x587b270
That will tell you what object it is trying to send the message to.
Third, you are not checking the error. You should always check the error before you start manipulating the array returned.