Locations in Core Data sorted by distance via NSFetchedResultsController?

浪尽此生 提交于 2019-11-29 06:55:16

(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.

malhal

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!