为啥没有多个 NSSortDescriptor 与 NSFetchedResultsController 一起工作

Posted

技术标签:

【中文标题】为啥没有多个 NSSortDescriptor 与 NSFetchedResultsController 一起工作【英文标题】:Why aren't multiple NSSortDescriptors working with NSFetchedResultsController为什么没有多个 NSSortDescriptor 与 NSFetchedResultsController 一起工作 【发布时间】:2012-08-07 19:03:04 【问题描述】:

我有一个带有 UITableView 的控制器,它显示“项目”对象的列表,这些项目就像支票簿条目。除了排序之外,一切似乎都正常工作。我将在下面描述我正在寻找的排序,但目前记录是按照它们创建的顺序显示的。 Item 对象 (NSManagedObjects) 的属性包括日期 (NSDate)、金额 (NSDecimalNumber) 和 entry(Entry 是一个与 Item 有关系的 NSManaged 对象)。

在应用程序中,您创建一个具有类型(NSNumber 0 == 贷方,1 = 借方)、标题、描述等的条目。然后添加 1 个或多个具有金额、日期等的 Item 对象。

我希望在表格视图部分中按日期对项目进行分组,以便共享相同日期的所有项目都在同一部分中。在每个部分中,它们应首先按类型(先贷后借)然后按金额降序排序。在一个部分中是这样的:

2012 年 7 月 3 日

信用:900 借:(25) 借:(15)

2012 年 8 月 7 日

信用:1000 信用:900 信用:100 信用:25 借方:(700) 借方:(450) 借:(25)

ViewController 本身是一个 UITableViewDelegate、UITableViewDataSource 和 NSFetchedResultsControllerDelegate。

这里是 NSFetchedResultsController getter 方法:

- (NSFetchedResultsController *)aFetchedResultsController 

    if (_aFetchedResultsController != nil) 
        return _aFetchedResultsController;
    

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(date >= %@) AND (date <= %@)", startDate, endDate];
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Item" inManagedObjectContext:self.context];
    [fetchRequest setEntity:entity];
    [fetchRequest setPredicate:predicate];

    NSSortDescriptor *sortDate = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:YES];
    NSSortDescriptor *sortType = [[NSSortDescriptor alloc] initWithKey:@"entry.type" ascending:YES];
    NSSortDescriptor *sortAmount = [[NSSortDescriptor alloc] initWithKey:@"amount" ascending:NO];
    [fetchRequest setSortDescriptors:[NSArray arrayWithObjects:sortDate, sortType, sortAmount, nil]];


    [fetchRequest setFetchBatchSize:20];

    NSFetchedResultsController *theFetchedResultsController =
    [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                        managedObjectContext:self.context sectionNameKeyPath:@"day"
                                                   cacheName:@"Root"];
    self.aFetchedResultsController = theFetchedResultsController;
    _aFetchedResultsController.delegate = self;

    return _aFetchedResultsController;

//end 

查看已加载:

- (void)viewDidLoad

    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.

    NSDate *today = [NSDate new];
    NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    NSDateComponents *offsetComponents = [[NSDateComponents alloc] init];

    [offsetComponents setMonth:-1]; //1 month ago
    startDate = [gregorian dateByAddingComponents:offsetComponents toDate:today options:0];

    [offsetComponents setMonth:1]; //1 month ahead
    endDate = [gregorian dateByAddingComponents:offsetComponents toDate:today options:0];



    //set the MOC
    self.context = [((AppDelegate *)[[UIApplication sharedApplication] delegate]) managedObjectContext];

    [self fetchRecords];

//end viewDidLoad

以及实际获取记录的方法:

-(void)fetchRecords

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(date >= %@) AND (date <= %@)", startDate, endDate];
    [[self.aFetchedResultsController fetchRequest] setPredicate:predicate];
    [NSFetchedResultsController deleteCacheWithName:@"Root"];

    NSError *error;
    if (![[self aFetchedResultsController] performFetch:&error]) 

        // Update to handle the error appropriately.
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        exit(-1);  // Fail

    //end if


    [_tableView reloadData];

//end fetchRecords

“day”是我用来让 UITableView 部分标题显示格式化日期字符串的 Item 实例方法:

- (NSString *)day 
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    dateFormatter.dateStyle = NSDateFormatterFullStyle;

    return [dateFormatter stringFromDate:self.date];

//end day

【问题讨论】:

不应该是sectionNameKeyPath:@"date"吗?或者你有两个属性dateday?在这种情况下,两者都必须生成相同的相对排序。 - 顺便说一句:什么不起作用? 更新了问题以包含“day”方法的代码并概述问题是记录是按创建日期排序的,而不是我的特定排序方案。 【参考方案1】:

来自initWithFetchRequest:managedObjectContext:sectionNameKeyPath:cacheName:的文档

sectionNameKeyPath

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

您的代码中并非如此。将日期转换为字符串不会保留顺序。

Apple 开发者库中有示例代码DateSectionTitles 演示了如何正确执行此操作。

【讨论】:

【参考方案2】:

事实证明,上面所有的代码都可以完美运行,并且排序的工作方式确实没有错误。我的“day”函数掩盖了这个问题,该函数根据项目的“date”属性的NSDateFormatterFullStyle 表示制作 UITableView 部分。然而现实情况是,当涉及到时间部分时,日期都是不同的,这解释了为什么它们按创建顺序排序。

我在尝试 Martin R 关于使用 sectionNameKeyPath:@"date" 的评论后发现了这一点,并在其自己的部分中看到了每一行。

因此,我的问题的解决方案是仅在设置“日期”属性时设置月、日和年,这对于我的应用程序可能是唯一的,因为这些是我关心的唯一日期部分大约,不是时间。

为此,我使用以下代码覆盖了我的 NSManagedObject“项目”中“日期”的设置器:

- (void)setDate:(NSDate *)date


    NSDateComponents *components = [[NSCalendar currentCalendar] 
                                    components:NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit 
                                    fromDate:date];
    NSInteger day = [components day];
    NSInteger month = [components month];
    NSInteger year = [components year];

    NSDateComponents *comps = [[NSDateComponents alloc] init];

    [comps setDay:day];
    [comps setMonth:month];
    [comps setYear:year];

    NSCalendar *gregorian = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];

    NSDate *newDate = [gregorian dateFromComponents:comps];

    [self willChangeValueForKey:@"date"];
    [self setPrimitiveValue:newDate forKey:@"date"];
    [self didChangeValueForKey:@"date"];

//end setDate

【讨论】:

以上是关于为啥没有多个 NSSortDescriptor 与 NSFetchedResultsController 一起工作的主要内容,如果未能解决你的问题,请参考以下文章

当第一个描述符是 NSDate 时,多个 NSSortDescriptor 不起作用

NSFetchedResultsController 与 NSSortDescriptor 一对一

NSFetchedResultsController 与许多 NSSortDescriptor

CKQuery 的 distanceToLocation 上没有 NSSortDescriptor?

NSSortDescriptor 与关系

NSSortDescriptor 对 Core Data NSSet 对象进行排序的最有效方法