NSFetchedresultsController sectionName KeyPath 基于当前位置和其他位置之间的范围

Posted

技术标签:

【中文标题】NSFetchedresultsController sectionName KeyPath 基于当前位置和其他位置之间的范围【英文标题】:NSFetchedresultController sectionNameKeyPath based on range between current location and other location 【发布时间】:2014-04-23 10:23:47 【问题描述】:

我有一个entity,里面有对象。这些对象有Latitudelongitude。我还添加了一个 transient attribute range 来计算范围。请看下面的类:

@implementation Relation

@dynamic firstLetter;
@dynamic rel_address;
@dynamic rel_balanceTotal;
@dynamic rel_bank_country_code;
@dynamic rel_bank_number;
@dynamic rel_city;
@dynamic rel_city_id;
@dynamic rel_code;
@dynamic rel_country;
@dynamic rel_country_code;
@dynamic rel_customerProspect;
@dynamic rel_email;
@dynamic rel_expired_total;
@dynamic rel_fax;
@dynamic rel_gsm;
@dynamic rel_name;
@dynamic rel_phone;
@dynamic rel_turnovertotal;
@dynamic rel_vat_country_code;
@dynamic rel_vat_number;
@dynamic rel_website;
@dynamic rel_zipcode;
@dynamic rel_longitude;
@dynamic rel_latitude;
@dynamic range;

-(NSNumber *)range
    NSNumber *rangeFromCurrentLocation;

    [self willAccessValueForKey:@"range"];
    AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;

    CLLocation *location = [[CLLocation alloc]initWithLatitude:[self.rel_latitude floatValue] longitude:[self.rel_longitude floatValue]];
    CLLocation *location2 = [[CLLocation alloc]initWithLatitude:appDelegate.latitude longitude:appDelegate.longitude];

    CLLocationDistance distance = [location2 distanceFromLocation:location];  //      distance is expressed in meters

    CLLocationDistance kilometers = distance / 1000.0;

    float floatrange = ceil(kilometers / 5.0) * 5;
    rangeFromCurrentLocation = [NSNumber numberWithFloat:floatrange];

    [self didAccessValueForKey:@"range"];
    NSLog(@"RANGE IS %@",rangeFromCurrentLocation);

    return rangeFromCurrentLocation;

我想要做的是建立一个包含 5 公里、10 公里、15 公里、...的路段的列表。 在启动时,我只加载半径为 5 公里的关系。我就是这样做的。

_searchDistance = 2.50;
AppDelegate *appDelegate = [UIApplication sharedApplication].delegate;

float minLat = appDelegate.latitude - (_searchDistance / 69);
float maxLat = appDelegate.latitude + (_searchDistance / 69);
float minLon = appDelegate.longitude - _searchDistance / fabs(cos(appDelegate.longitude / 180.0 * M_PI)*69);
float maxLon = appDelegate.longitude + _searchDistance / fabs(cos(appDelegate.longitude / 180.0 * M_PI)*69);

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription
                               entityForName:@"Relation" inManagedObjectContext:context];
[fetchRequest setEntity:entity];
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"rel_latitude <= %f AND rel_latitude >= %f AND rel_longitude <= %f AND rel_longitude >= %f", maxLat, minLat, maxLon, minLon];
[fetchRequest setPredicate:predicate];
NSSortDescriptor *sort = [[NSSortDescriptor alloc]
                          initWithKey:@"rel_name" ascending:YES selector:@selector(caseInsensitiveCompare:)];
[fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sort,nil]];

这工作正常,我只得到radius of 5km 中的对象。在我的 tableView 的底部,我有一个 load more button,我在其中更改了 _searchDistance(我又增加了 5 公里)

为了建立我的列表,我使用这个NSFetchedresultController

NSFetchedResultsController *theFetchedResultsController =
    [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                        managedObjectContext:context sectionNameKeyPath:@"range"
                                                   cacheName:nil];

问题

当视图加载时,一切正常。但是当我点击加载更多时,我得到一个空列表并在我的日志中得到这个错误

CoreData: error: (NSFetchedResultsController) The fetched object at index 6 has an out of order section name '10. Objects must be sorted by section name'
2014-04-23 12:01:27.673 Adsolut[2097:60b] Unresolved error Error Domain=NSCocoaErrorDomain Code=134060 "The operation couldn’t be completed. (Cocoa error 134060.)" UserInfo=0x167630f0 reason=The fetched object at index 6 has an out of order section name '10. Objects must be sorted by section name', 
    reason = "The fetched object at index 6 has an out of order section name '10. Objects must be sorted by section name'";

任何帮助将不胜感激! 提前致谢!

【问题讨论】:

【参考方案1】:

问题是所有对象都先按照第一个排序描述符排序, 然后根据sectionNameKeyPath分组。 documentation 声明了这个参数:

如果这个key path和第一次排序指定的不一样 fetchRequest 中的描述符,它们必须生成相同的相对 订购。

这意味着您必须使用对所有对象进行排序的第一个排序描述符 根据他们到当前位置的距离。不幸的是,这种排序描述符 只能使用 persistent 属性。但是(据我所知)没有办法解决这个问题 如果您使用获取的结果控制器,则会受到限制。

【讨论】:

那你建议我怎么做? @StefGeelen:您可以计算距离并将其作为(永久)属性存储在实体中。当当前位置发生变化时,您必须重新计算它。 - 或者不使用 FRC 并实现您自己的代码将对象分组到部分中。 - 两种解决方案都不是 100% 令人满意,但这是 FRC 的限制。【参考方案2】:

我同意@Martin R.

我使用的方法是在我的NSFetchedResultsController 中构建逻辑,这样,如果存在,第一个排序描述符始终是section,其他排序描述符添加到排序描述符数组中作为第二个,第三个,第四,等等。

通过这种方式,您可以保护您的 FRC 免受当前遇到的崩溃。

更新...

为了进一步讨论,我已经包含了我经过稍微编辑的方法来准备 FRC,其中包括针对您遇到的崩溃的保护。如前所述,这可能不适用于瞬态属性。

此代码在触发方法之前设置了以下公共属性。

@property (nonatomic, strong) NSString *entity;
@property (nonatomic, strong) NSString *sectionIdentifier;
@property (nonatomic, strong) NSString *sortAttributePrimary;
@property (nonatomic, strong) NSString *sortAttributeSecondary;
@property (nonatomic, strong) NSString *sortAttributeTertiary;
@property (nonatomic, strong) NSString *sortAttributeQuaternary;
@property (nonatomic, strong) NSPredicate *requestPredicate;
@property (nonatomic, strong) NSString *cacheName;

这些属性中的每一个都由调用 FRC 的实体对象设置。当sectionIdentifier 存在值时,默认设置sortAttributePrimary = nil

还要注意(BOOL)userSettingListsSortAscending 是一个NSUserDefault,它也在此方法的第一部分中确定,但已被删除以尝试将代码保持在合理的大小。

- (void)configureFetch 
    if (self.managedObjectContext) 

        ...< other code including preparation of NSPredicate>...

        //  Prepare and set Sort Descriptors
        NSArray *requestSortDescriptors = nil;
        NSSortDescriptor *sortDescriptorPrimary = nil;
        NSSortDescriptor *sortDescriptorSecondary = nil;
        NSSortDescriptor *sortDescriptorTertiary = nil;
        NSSortDescriptor *sortDescriptorQuaternary = nil;

        if (self.sortAttributePrimary != nil) 
            sortDescriptorPrimary = [NSSortDescriptor sortDescriptorWithKey:self.sortAttributePrimary ascending:userSettingListsSortAscending selector:@selector(compare:)];
         else if (self.sortAttributePrimary == nil) 
            sortDescriptorPrimary = [NSSortDescriptor sortDescriptorWithKey:self.sectionIdentifier ascending:userSettingListsSortAscending];
        

        if (self.sortAttributeSecondary != nil) 
            sortDescriptorSecondary = [NSSortDescriptor sortDescriptorWithKey:self.sortAttributeSecondary ascending:userSettingListsSortAscending selector:@selector(compare:)];
        

        if (self.sortAttributeTertiary != nil) 
            sortDescriptorTertiary = [NSSortDescriptor sortDescriptorWithKey:self.sortAttributeTertiary ascending:userSettingListsSortAscending selector:@selector(compare:)];
        

        if (self.sortAttributeQuaternary != nil) 
            sortDescriptorQuaternary = [NSSortDescriptor sortDescriptorWithKey:self.sortAttributeQuaternary ascending:userSettingListsSortAscending selector:@selector(compare:)];
        

        if (sortDescriptorPrimary != nil) 
            if (sortDescriptorSecondary != nil) 
                if (sortDescriptorTertiary != nil) 
                    if (sortDescriptorQuaternary != nil) 
                        requestSortDescriptors = [NSArray arrayWithObjects: sortDescriptorPrimary, sortDescriptorSecondary, sortDescriptorTertiary, sortDescriptorQuaternary, nil];
                     else if (sortDescriptorQuaternary == nil) 
                        requestSortDescriptors = [NSArray arrayWithObjects: sortDescriptorPrimary, sortDescriptorSecondary, sortDescriptorTertiary, nil];
                    
                 else if (sortDescriptorTertiary == nil) 
                    requestSortDescriptors = [NSArray arrayWithObjects: sortDescriptorPrimary, sortDescriptorSecondary, nil];
                
             else if (sortDescriptorSecondary == nil) 
                requestSortDescriptors = [NSArray arrayWithObjects: sortDescriptorPrimary, nil];
            
        
        NSLog(@"%@ - %@ - sortDescriptors are: %@_", self.className, NSStringFromSelector(_cmd), requestSortDescriptors);
        [fetchRequest setSortDescriptors:requestSortDescriptors];

        //  Set fetchedResultsController
        if (self.sectionIdentifier == nil || [self.sectionIdentifier isEqualToString:@""]) 
            self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:self.cacheName];
         else 
            self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:self.sectionIdentifier cacheName:self.cacheName];
        
        self.fetchedResultsController.delegate = self;
     else 
        self.fetchedResultsController = nil;
    

最后值得注意的是,我使用compare:而不是localizedCaseInsentiveCompare:进行排序,因为有时我的排序描述符之一是NSDate

【讨论】:

有很多示例,其中节键与第一个排序描述符不同。在这个问题中,第一个排序描述符可能是到当前位置的距离,而 section key 可能是四舍五入到 5(km)的倍数的距离。 - 另一个例子是当对象按日期排序时,部分键只是年-月-日。 @MartinR 你建议我能做什么? 是否值得发布我的代码来说明我如何做到这一点?我承认我没有使用瞬态属性作为我的部分,但它可能会为解决方案提供一些线索。 @andrewbuilder 任何帮助表示赞赏!

以上是关于NSFetchedresultsController sectionName KeyPath 基于当前位置和其他位置之间的范围的主要内容,如果未能解决你的问题,请参考以下文章

在 Core Data 应用程序中调用 performFetch 后,是不是需要手动更新表视图?