Locations in Core Data sorted by distance via NSFetchedResultsController?

I have a set of entity objects in my iOS Core Data database that describe something at a location. Let's call the entity Location. I have implemented this by having two attributes on Location that refer to the location - latitude and longitude, both doubles. There are other elements, like name.

I am using a NSFetchedResultsController to bind the entities to a UITableViewController. What I would like to do is have the results sorted by distance to a given CLLocationCoordinate2D. In an really ideal scenario, I'm able to refresh that list to recalculate the sort based on a new location. Therefore, this sort would depend on two keys, and a third "static" variable (one that doesn't vary across the items in the collection).

I think I could figure out how to do this if I was sorting an arbitrary list with NSSortDescriptors. However, I don't control how the sort descriptors are used in an NSFetchedResultsController.

Is there a way that I could configure my entities, my NSFetchedResultsController, my NSSortDescriptors, etc. to accomplish this? I suspect that the answer lies not in creating a fancy NSSortDescriptor but instead in creating a transient attribute in the entity that represents the distance-to-me, and recalculating that attribute periodically. However, I'm new enough to Core Data that I'm not sure how to best do this (iterate over all entities and recalculate a field). I'm also not sure if NSSortDescriptors will work on Transient attributes.


(From the comments:)

A fetch request for a (SQLite based) Core Data store cannot use sort descriptors based on transient attributes or Objective-C based predicates.

If you don't want to loose the advantages of a fetched results controller (like animated table view updates, automatic grouping into sections etc.) you have to pre-compute the distance to the current location and store it in a (persistent) attribute of your objects.

Alternatively, you could fetch all objects and sort them in memory. In that case you can use arbitrary sort descriptors. But that cannot be combined with a fetched results controller, so you would have to register for changes in the managed object context and reload the table if necessary.


I discovered the BSFetchedResultsController github project that is a NSFetchResultsController subclass that does what Martin suggested in that it sorts in memory using a arbitrary sort descriptor, furthermore it also registers for changes to the context and calculates any index changes again taking into account the arbitrary sort descriptor. All in all a very impressive feat! I successfully used it to sort by distance as follows:

BSFetchedResultsController* fetchedResultsController = [[BSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];

//  create a location to compare distance to, e.g. current location
CLLocation* sourceLocation = [[CLLocation alloc] initWithLatitude:55.87595153937809 longitude:-4.2578177698913855];

// compare the distance from both to the source location
fetchedResultsController.postFetchComparator= ^(id a, id b) {
    Venue* v1 = (Venue*)a;
    Venue* v2 = (Venue*)b;

    double d1 = [v1.coreLocation distanceFromLocation:sourceLocation];
    double d2 = [v2.coreLocation distanceFromLocation:sourceLocation];

    return [@(d1) compare:@(d2)];
};

NSError *error = nil;
if (![fetchedResultsController performFetch:&error]) {
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
}

Since its an old project it doesn't have ARC, so when you include the two files remember to mark the .m with Compiler Flags -fno-objc-arc in the Target, Build Phases. Also be aware the developer thinks the code is not production ready so be sure to do adequate testing if using it.

In my code above I have a transient property coreLocation on my Venue managed object subclass, you can see how to achieve that here. Also the distance calculation is inefficient, you might want to cache the distance in the object rather than re-calculate it every comparison.

Finally, it appears this project came to be because of the creator Daniel Thorpe's Stackoverflow question going unanswered, causing him to solve the problem and post the only answer himself, so I think if you find his project useful you could kindly up-vote his post as I did.

链接地址: http://www.djcxy.com/p/36148.html

上一篇: 一个NSFetchedResultsController

下一篇: 核心数据中的位置通过NSFetchedResultsController按距离排序?