Sorting an array of NSURL objects by creation date - iphone

My caches images to a directory (/Library/Caches/ImageCache/). When the directory exceeds a certain size I would like to delete the oldest file in the directory. To accomplish this task I use NSFileManager to retrieve the directory contents. I then try to sort this array by date and delete the oldest object.
My problem is that my program crashes when I try to sort the array by the key NSURLCreationDateKey.
NSFileManager *defaultFM = [NSFileManager defaultManager];
NSArray *keys = [NSArray arrayWithObjects:NSURLNameKey, NSURLCreationDateKey, nil];
NSURL *cacheDirectory = [self photoCacheDirectory]; // method that returns cache URL
NSArray *cacheContents = [defaultFM contentsOfDirectoryAtURL:cacheDirectory
includingPropertiesForKeys:keys
options:NSDirectoryEnumerationSkipsSubdirectoryDescendants
error:nil];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:NSURLCreationDateKey ascending:YES];
NSArray *sortedArray = [cacheContents sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
The program crashes on the last line. With error:
* Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ valueForUndefinedKey:]: this class is not key value coding-compliant for the key NSURLCreationDateKey.'

EDIT : Better answer
If that doesn't work, you will have to write your own comparator block and get the dates to compare manually :(
[cacheContents sortUsingComparator:^ (NSURL *a, NSURL *b) {
// get the two dates
id da = [[a resourceValuesForKeys:[NSArray arrayWithObject:NSURLCreationDateKey] error:nil] objectForKey:NSURLCreationDateKey];
id db = [[b resourceValuesForKeys:[NSArray arrayWithObject:NSURLCreationDateKey] error:nil] objectForKey:NSURLCreationDateKey];
// compare them
return [da compare:db];
}];
(Same disclaimer still applies but I'm not even sure that will compile ;) - you get the idea though!)
Here is my first answer (included here for posterity but mostly it just shows how important it is to read the question properly :)
It's because you're getting an array of NSURL objects; these don't have a NSURLCreationDateKey property.
Try this (disclaimer - not 100% it will work)
NSString *key = [NSString stringWithFormat:#"fileAttributes.%#", NSURLCreationDateKey];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:key ascending:YES];
Your key to sort by is a property of the fileAttributes dictionary which is, in turn, a property of your enumerator.

I'm not positive about this, but I think that the contentsOfDirectoryAtURL: method returns a list of URLs which are not KVC objects. To get the property you want from the NSURL, you need to call:
getResourceValue:forKey:error:
Since this is not KVC compatible, you won't be able to use a sort descriptor, and instead need to use a routine like [cacheContents sortedArrayUsingComparator:...]

Related

How to sort an NSArray full of EKCalendars by the title

I've got a NSArray with a bunch of EKCalendar Objects in it. I need to sort them alphabetically. I'm new to selectors but I think I need something like...
NSArray *array = [otherArray sortedArrayUsingSelector:#selector('localizedCaseInsensitiveCompare:title')];
Cheers
You cannot do it that way. Instead do the following:
NSArray *sortedArray = [array sortedArrayUsingComparator: ^(id obj1, id obj2) {
EKCalendar *cal1 = (EKCalendar *)obj1;
EKCalendar *cal2 = (EKCalendar *)obj2;
return [cal1.title localizedCaseInsensitiveCompare:cal2.title];
}];
Edit - an explanation:
-sortedArrayUsingComparator takes what is called a 'block' (an inline function) that must return an NSComparisonResult. All the hard work is done for you, as your block is run for as many pairs of objects as is needed to establish the correct order. Then all this does is cast each object type to an EKCalendar and then compare the two titles. You can adapt this to work for any type of object.
This should do the trick:
NSMutableArray *sortDescriptors = [NSMutableArray array];
NSSortDescriptor *sortByTitleAsc = [[[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES] autorelease];
[sortDescriptors addObject:sortByTitleAsc];
NSArray *arraySortedByTitle = [otherArray sortedArrayUsingDescriptors:sortDescriptors];
No, you don't want to use a selector, you want to use a key path, which requires a sort descriptor. You can't append an arbitrary property name to a selector name. The selector must exactly match the method name. Otherwise you just get nothing (nil/NULL/0) for the selector.
id sortedArray = [array sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES]]];
For the sake of completeness and and timeliness, here’s the Swift 4 version:
let store = EKEventStore()
let calendars = store.calendars(for: .event)
let calendarsSorted = calendars.sorted { $0.title.localizedCaseInsensitiveCompare($1.title) == ComparisonResult.orderedAscending }
By the way, please don’t forget to request access to the store by calling store.requestAccess(to:completion:) before accessing the store’s data.

CoreData Math Functions

I've got a column with integer values in CoreData. While retrieving results from it, I want the column values to be subtracted with a number.
Something like: columnValue - someNumber (this number is entered by user)
I know I may have to use NSPredicate for this, but am unaware if there's a function or syntax for it.
The alternate right now I have is to iterate all column values and subtract with 'someNumber'. But I think there should be a better and efficient way to do this.
Edit: Code from #salo.dm 's answer
- (NSDictionary *)myFetchResults {
//Predicate works fine
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:[NSArray arrayWithObjects:pred1, pred2, nil]];
/*Sort Descroptor - Sorting by 4 columns*/
NSSortDescriptor *sortDesc1 = [NSSortDescriptor sortDescriptorWithKey:#"Column1" ascending:YES];
NSSortDescriptor *sortDesc2 = [NSSortDescriptor sortDescriptorWithKey:#"Column2" ascending:YES];
NSSortDescriptor *sortDesc3 = [NSSortDescriptor sortDescriptorWithKey:#"Column3" ascending:YES];
NSSortDescriptor *sortDesc4 = [NSSortDescriptor sortDescriptorWithKey:#"Column4" ascending:YES];
/*Get Data*/
MyAppDelegate *appDelegate = [[UIApplication sharedApplication] delegate];
NSManagedObjectContext *context = [appDelegate managedObjectContext];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"TableName" inManagedObjectContext:context];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
[fetchRequest setPredicate:predicate];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDesc1, sortDesc2, sortDesc3, sortDesc4, nil]];
NSArray *listData = [context executeFetchRequest:fetchRequest error:nil];
/*Create subtract expression*/
NSExpressionDescription *subExp1 = [[NSExpressionDescription alloc] init];
[subExpLatitude setName:#"subtraction1"];
[subExpLatitude setExpression:[NSExpression expressionForFunction:#"from:subtract:"
arguments:[NSArray arrayWithObjects:
[NSExpression expressionForKeyPath:#"Column3"],
[NSExpression expressionForConstantValue:[NSNumber numberWithDouble:someNumber1]],
nil]]];
[subExp1 setExpressionResultType:NSDoubleAttributeType];
NSExpressionDescription *subExp2 = [[NSExpressionDescription alloc] init];
[subExpLongitude setName:#"subtraction2"];
[subExpLongitude setExpression:[NSExpression expressionForFunction:#"from:subtract:"
arguments:[NSArray arrayWithObjects:
[NSExpression expressionForKeyPath:#"Column4"],
[NSExpression expressionForConstantValue:[NSNumber numberWithDouble:someNumber2]],
nil]]];
[subExp2 setExpressionResultType:NSDoubleAttributeType];
/*Get difference data*/
[fetchRequest setResultType:NSDictionaryResultType];
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:subExp1, subExp2, nil]];
NSArray *listDifference = [context executeFetchRequest:fetchRequest error:nil];
NSLog(#"Subtraction 1: %#", [[listDifference objectAtIndex:0] objectForKey:#"subtraction1"]);
NSLog(#"Subtraction 2: %#", [[listDifference objectAtIndex:0] objectForKey:#"subtraction2"]);
NSMutableDictionary *dictResult;
[dictResult setObject:listData forKey:#"Data"]
[dictResult setObject:listDifference forKey:#"Difference"]
return dictResult;
}
Edit: Get coredata object
This doesn't work.
NSExpressionDescription *expEntity = [[NSExpressionDescription alloc] init];
[expEntity setName:#"TableNameEntity"];
[expEntity setExpression:[NSExpression expressionForKeyPath:#"objectID"]]; //Searches for a column for the name specified
[expEntity setExpressionResultType:NSObjectIDAttributeType];}
Had to change it to below to get it working (Assuming this is the correct way)
NSExpressionDescription *expEntity = [[NSExpressionDescription alloc] init];
[expEntity setName:#"TableNameEntity"];
[expEntity setExpression:[NSExpression expressionForEvaluatedObject]];
[expEntity setExpressionResultType:NSObjectIDAttributeType];
I added expEntity to the setPropertiesToFetch list. Now I get two values in the dictionary.
{
TableNameEntity = "0x5e22120 <x-coredata://1A659A52-9321-4ACD-992B-04F20E7BDCED/TableNameEntity/p1640>";
subtractionValue = "-24.13";
}
When I try to retrieve and access TableNameEntity from the dictionary, the app crashes.
TableNameEntity *tableEntity = (TableNameEntity *)[dict objectForKey:#"TableNameEntity"];
tableEntity.column1 //This is not the exact code. But this operation crashes with error
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_NSObjectID_48_0 column1]: unrecognized selector sent to instance 0x5e22120'
Here if you notice, the value for key TableNameEntity is contained in quotes, so I guess its being returned as a string.
See if you can correct what I've done wrong.
I've tried an alternate to get columns values in the dictionary. Here it is (this works fine). But I guess its not a good approach.
[fetchRequest setPropertiesToFetch:[NSArray arrayWithObjects:subExp1, #"column1", #"column2", ... #"columnN" nil]];
You could make the calculation in a fetch request as follows:
- (NSArray *)myFetchResults
{
NSFetchRequest *request = [[NSFetchRequest alloc] init];
request.entity = [NSEntityDescription entityForName:#"myEntity" inManagedObjectContext:myContext];
request.resultType = NSDictionaryResultType;
NSExpressionDescription *subExDescr = [[NSExpressionDescription alloc] init];
[subExDescr setName:#"subtraction"];
[subExDescr setExpression:[NSExpression expressionForFunction:#"subtract:from:"
arguments:[NSArray arrayWithObjects:
[NSExpression expressionForConstantValue:[NSNumber numberWithInt:someNumber]],
[NSExpression expressionForKeyPath:#"myAttribute"],
nil]]];
[subExDescr setExpressionResultType:NSInteger64AttributeType];
request.propertiesToFetch = [NSArray arrayWithObject:subExDescr, nil];
NSError *err = nil;
NSArray *results = [self.moContext executeFetchRequest:request error:&err];
[request release];
[err release];
return results;
}
The fetch results will be an array of dictionaries. You can access the result for the nth value in the column as follows:
NSArray *results = [self myFetchResults];
NSDictionary *nthDict = [results objectAtIndex:n];
NSInteger nthValue = [nthDict objectForKey:#"subtraction"];
Note that this code is untested. As is, I believe it will operate on all items in the column. If you want to operate only on selected items, you can add a predicate to select the items you want to operate on.
You could also look up the documentation for NSExpression and build all sorts of different operations. The class is a bit dense, but the Apple documentation has some snippets of code that help to understand how to use it. And I hope the above example illustrates how to incorporate it into a fetch request.
EDIT: CORRECTION
The entity, of course, has to be specified in the fetch request. I had initially left that out, but have now corrected the code.
EDIT: RESPONSE TO COMMENT
I'm not sure I understand what you're asking, but this may be it. You can create expression descriptions as follows:
NSExpressionDescription *expLatitude = [[NSExpressionDescription alloc] init];
[expLatitude setName:#"latitude"];
[expLatitude setExpression:[NSExpression expressionForKeyPath:#"Column3"]];
[expLatitude setExpressionResultType:NSDoubleAttributeType];
NSExpressionDescription *expEntity = [[NSExpressionDescription alloc] init];
[expEntity setName:#"TableNameEntity"];
[expEntity setExpression:[NSExpression expressionForKeyPath:#"objectID"]];
[expEntity setExpressionResultType:NSObjectIDAttributeType];}
Then, you add them to the propertiesToFetch array, as two more objects. Each dictionary in the fetch results will now have the latitude, the subtraction resulting from that same latitude, and the corresponding objectID of the entity that contained that latitude. The dictionaries will be ordered in the results array according to your sort descriptors. (I haven't tried the objectID expression, but I think it should work fine.)
Basically, your results are ordered in the exact same order as a traditional fetch request with the same predicate and the same sort descriptors, that is, for a fetch request with the default result type NSManagedObjectResultType.
I hope this answers your question. If not, don't hesitate to ask again. But, I may take a while to answer because it's sleep time for me now.
EDIT: RESPONSE TO 'GET COREDATA OBJECT'
Good catch on finding the correct expression to get the object ID! (Seeing it, the expression I offered for this now looks obviously wrong.)
As to the exception you're getting, it makes sense. The value returned in the fetch results is not the managed object itself, it's only the managed object's ID. To access the managed object, I think the following should work:
NSManagedObjectID *myObjectID = [dict objectForKey:#"TableNameEntity"];
TableNameEntity *tableEntity = (TableNameEntity *)[context objectWithID:myObjectID];
tableEntity.column1
The context above is the NSManagedObjectContext.
However, I think I prefer your final solution. I didn't know you could combine NSExpressionDescriptions with properties in the propertiesToFetch array. Good to know!
More importantly, it may be faster to get all the properties you need in the fetch than to get only the objectID from the fetch and get the properties later. Getting the objectID generally does not fire the fault for the entity. I believe the fault will be fired later, when you access the properties. It will fire once, when accessing the first property, or multiple times, once for each property. (I'm not sure which.) [For an explanation of faulting, see Firing Faults.]
My recommendation is that including all the properties you need in propertiesToFetch is the best approach. (You may try getting the objectID, if you prefer. But, if you find it's slow, you can go back to getting all the properties in the fetch.)
Fetch requests and expressions are poorly documented. You have to play with them a bit to get the syntax right. You seem to be doing very well.

How to sort NSArray of objects based on one attribute

I am trying to sort an array of managed objects alphabetically. The attribue that they need to be sorted by is the name of the object (NSString) with is one of the managed attributes. Currently I am putting all of the names in an array of strings and then using sortedNameArray = [sortedNameArray sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)]; and then enumerating them back into an array with the objects. This falls apart when two names are the same so I really need to be able to sort by one attribute. How should I go about doing this?
Use NSSortDescriptor. Just search the documentation on it and there some very simple examples you can copy right over. Here is a simplified example:
NSSortDescriptor *valueDescriptor = [[NSSortDescriptor alloc] initWithKey:#"MyStringVariableName" ascending:YES];
NSArray *descriptors = [NSArray arrayWithObject:valueDescriptor];
NSArray *sortedArray = [myArray sortedArrayUsingDescriptors:descriptors];
And just like that you have a sorted array.
You can do this by using NSSortDescriptor,
eg.
`NSSortDescriptor *valueDescriptor = [[NSSortDescriptor alloc]initWithKey:#"distance" ascending:YES];`
// Here I am sorting on behalf of distance. You should write your own key.
NSArray * descriptors = [NSArray arrayWithObject:valueDescriptor];
NSArray *sortedArray=[yourArray sortedArrayUsingDescriptors:descriptors];`

NSSortdescriptor, finding the path to the key I wish to use for sorting

I am using an NSSortdescriptor to sort a collection of NSArrays, I then came across a case where the particular NSArray to be sorted contains an NSDictionary who contains an NSDictionary.
I would like to sort from the string paired with a key in the last dictionary.
This is how I would reference the string:
NSDictionary *productDict = [MyArray objectAtIndex:index];
NSString *dealerName = [[productDict objectForKey:#"dealer"] objectForKey:#"name"];
How would I use the dealerName in my NSSortdescriptor to sort the array?
NSSortDescriptor * sortDesc = [[NSSortDescriptor alloc] initWithKey:/* ? */ ascending:YES];
sortedDealerArray = [value sortedArrayUsingDescriptors:sortDesc];
[sortDesc release];
Hope someone could help me a bit with how I go about sorting according to keys inside objects inside other objects:)
Thank you.
actually warrenM is right. You can just use keypaths. #"dealer.name" should work for above.

How do I get Attributes from Core Data into an Array for - iPhone SDK

I'm trying to retrieve data from Core Data and put it into a Mutable Array
I have an Entity called 'Stock' and in Properties, attributes called : code, price & description...
How do I get the data stored in these attributes into a simple Mutable Array?
I've added this code...
NSMutableArray *array = [[NSMutableArray alloc]init];
[array addObject:[stock valueForKey:#"code"]];
and I get this error...
'-[NSCFArray insertObject:atIndex:]: attempt to insert nil'
I have a 'Managed Object Class' called 'Stock' and declared called stock. Am I missing something?
If I do this in the -cellForRowAtIndexPath...
Stock *stock1 = [fetchedResultsController objectAtIndexPath:indexPath];
array = [[NSMutableArray alloc] init];
[array addObject:stock1.code];
NSLog(#"Filtered List is? %#", array);
In the console I can see these 2 items
'The Filtered array is 810005'
'The Filtered array is 810007
'
What must I do to get these items(810005 & 810007) into an array set up in the -viewDidLoad method? Like it does in the -cellForRowAtIndexPath?
Update
Hi Marcus,
Finally got it working (well, 80%)
I put this in the -cellForRowAtIndexPath
Stock *product = nil;
if (tableView == self.searchDisplayController.searchResultsTableView)
{
filteredListContent = [NSMutableArray arrayWithObjects:stock1.code, nil];
product = [self.filteredListContent objectAtIndex:indexPath.row];
[self configureFilteredCell:cell atIndexPath:indexPath];
[filteredListContent objectAtIndex:indexPath.row];
NSLog(#"Filtered List Array List is? %#", stock1.code);
}
else
{
listContent = [NSMutableArray arrayWithObjects:stock1.code, nil];
[self configureCell:cell atIndexPath:indexPath];
NSLog(#"List Array List is? %#", stock1.code);
}
Then I used this code in the scope
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
self.savedSearchTerm = searchText;
if (searchText !=nil)
{
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"code beginsWith[cd] %#", searchText];
[fetchedResultsController.fetchRequest setPredicate:predicate];
}
else
{
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"code contains[cd] %#", searchText];
[fetchedResultsController.fetchRequest setPredicate:predicate];
[self.tableView reloadData];
}
NSError *error = nil;
if (![[self fetchedResultsController] performFetch:&error])
{
// Handle error
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
exit(-1); // Fail
}
[self.tableView reloadData];
Everything is filtering fine but when I hit cancel on the search, it's not reloading the original data...
I won't be defeated...!!
Thanx
Since you are having this issue in your -viewDidLoad, I am guessing (and without the code from -viewDidLoad, it is only a guess) that you are trying to fetch objects from the NSFetchedResultsController before the -executeFetch: has been called on the controller and therefore you are in the land of nils.
I would suggest setting a break point in your -viewDidLoad and watching the values and you walk through your code. This will tell you what is nil and where.
Of course a better question is, why are you trying to put NSManagedObject instances into a NSMutableArray? Since they are already in your NSFetchedResultsController is there really a need to build up another array? What is the end goal?
Update
Now I understand what you are trying to do.
Solution 1
Only populate the array when a search has been conducted. Take a look at the http://developer.apple.com/iphone/library/samplecode/TableSearch/index.html example code and you should see how to apply it to your situation.
If you want to enter the table view with a pre-defined search then you need to perform it after you have executed a -performFetch: in the NSFetchedResultsController.
Solution 2
Modify the NSPredicate on the NSFetchedResultsController to include your search terms and then execute -performFetch: on the NSFetchedResultsController, you may have to do a -reloadData on the table as well, I am not sure.
When the user clears the search field you reset the predicate and re-fetch everything. Since it is all cached there should be no performance penalty.
Solution 2 just occurred to me and I have not tested it personally but there is no reason it shouldn't work just fine. Should even give you live updates within the search.
Have you read the documentation? You fetch your Stock instances (all of them or filter them with a predicate), then do with them whatever you please.
You can then add their properties to an array individually:
[array addObject:[stockInstance valueForKey:#"price"];
... or use a combination of < NSKeyValueCoding > protocol methods such as -dictionaryWithValuesForKeys: NSDictionary methods such as -objectsForKeys:notFoundMarker: to get an array for given keys.
This may or may not actually be what you need to do, though. It depends on what you intend to use the resulting array for. If you want a quick sum of all matching Stock instances' "price" values, for example, you can use Set and Array Operators. It really depends on what you're trying to achieve.
When I got your error,
'-[NSCFArray insertObject:atIndex:]: attempt to insert nil'
I had given the fetchedRequest a sort descriptor that had a nil key. The error appeared when I used these lines:
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:nil ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
The error disappeared when I set the key to #"name":
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
[fetchRequest setSortDescriptors:sortDescriptors];