向 NSFetchedResultsController 添加额外的部分

Posted

技术标签:

【中文标题】向 NSFetchedResultsController 添加额外的部分【英文标题】:Adding extra sections to a NSFetchedResultsController 【发布时间】:2010-11-01 16:14:13 【问题描述】:

我正在为我的公司编写一个小型 iPhone 应用程序,该应用程序一次显示每个员工一周的预订情况。我正在使用核心数据来获取给定一周的“预订”列表,并希望将它们显示在 UITableView 中,该 UITableView 分解为一周中的每一天。

问题在于我需要为一周中的每一天显示 7 个部分(在部分/日期没有预订的地方显示“无预订”单元格)。

我有一个应用程序的屏幕截图here(抱歉,我是 StackOverlow 的新手,所以还不能发布图片)

目前,我通过使用“fetchResults”方法来实现这一点,该方法获取预订并将它们组织到一系列可能的日期中:

- (void)refetchResults     

// Drop bookings Array, replacing with new empty one
// 7 slots for 7 days each holding mutable array to recieve bookings where appropraite
self.bookings = [NSArray arrayWithObjects:[NSMutableArray array],
                  [NSMutableArray array], [NSMutableArray array],
                  [NSMutableArray array], [NSMutableArray array],
                  [NSMutableArray array], [NSMutableArray array], nil];

// Create the fetch request for the entity.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Booking" inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];

// Limit to this weeks data
[fetchRequest setPredicate:
 [NSPredicate predicateWithFormat:@"(date >= %@) && (date <= %@) && (resource == %@)",
  firstDate,lastDate,resourceId]];

// Edit the sort key as appropriate.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:YES];
NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:@"recId" ascending:YES];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, sortDescriptor2, nil];
[fetchRequest setSortDescriptors:sortDescriptors];

// Fetch records in to array
NSError *error;
NSArray *results = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];
if (results == nil) 
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    abort();


[fetchRequest release];
[sortDescriptor release];
[sortDescriptor2 release];
[sortDescriptors release];

// Walk through records and place in bookings Array as required
for (Booking *item in results) 
    // Decide on array index by difference in firstDate and booking date
    int idx = (int)[[item date] timeIntervalSinceDate:firstDate]/86400;
    // Add the item to the approp MutArray
    [(NSMutableArray *)[bookings objectAtIndex:idx] addObject:item];


// Reload table
[tableView reloadData];

我的问题是:有什么方法可以使用 NSFetchedResultsController 达到相同的结果?不知何故,我需要让 NSFetchedResultsController 有 7 个部分,一个用于一周中的每一天,其中一些可能没有预订。

非常感谢任何帮助:)

【问题讨论】:

【参考方案1】:

因此,由于外面的天气不太好,我尝试回答自己的问题并实施我在给 westsider 的回复中描述的“解决方法”。

这个想法是保存一个“映射”数组(只是一个简单的 7 槽 int 数组),它将 tableview 要求的部分映射到底层的 fetchedresultscontroller 部分。每个数组槽都将具有适当的部分索引或“-1”,其中没有基础部分(并且应该显示“无预订”单元格)。

所以,我的 refetchResults 方法变成了:

- (void)refetchResults     

    // Create the fetch request for the entity.
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Booking" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

    // Limit to this weeks data
    [fetchRequest setPredicate:
     [NSPredicate predicateWithFormat:@"(date >= %@) && (date <= %@) && (resource == %@)",
      firstDate,lastDate,resourceId]];

    // Edit the sort key as appropriate.
    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"date" ascending:YES];
    NSSortDescriptor *sortDescriptor2 = [[NSSortDescriptor alloc] initWithKey:@"recId" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, sortDescriptor2, nil];
    [fetchRequest setSortDescriptors:sortDescriptors];

    // Set up FRC
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"date" cacheName:nil];
    self.fetchedResultsController = aFetchedResultsController;
    self.fetchedResultsController.delegate = self;
    [aFetchedResultsController release];
    [fetchRequest release];
    [sortDescriptor release];
    [sortDescriptor2 release];
    [sortDescriptors release];

    // Run up FRC
    NSError *error = nil;
    if (![fetchedResultsController_ performFetch:&error]) 
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    

    // Update FRC map
    [self updateFRCMap];

    // Reload table
    [tableView reloadData];

映射设置如下。每当需要刷新映射时都会调用此方法 - 例如,当我从 fetchedresultscontroller 获取已添加/删除/等项目的回调时。

- (void)updateFRCMap 

    // Set mapping table for seven days of week to appropriate section in frc
    for (int idx=0;idx<7;idx++)  frcMap[idx] = -1;    // Reset mappings
    // For each section
    for (int sidx=0; sidx<[[self.fetchedResultsController sections] count]; sidx++) 
    
        // If section has items
        if ([[[self.fetchedResultsController sections] objectAtIndex:sidx] numberOfObjects] > 0) 
        
            // Look at first booking of section to get date
            NSDate *date = [(Booking *)[self.fetchedResultsController objectAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:sidx]] date];
            // Decide on array index by difference in firstDate and booking date
            int idx = (int)[date timeIntervalSinceDate:firstDate]/86400;
            // Set map
            frcMap[idx] = sidx;
        
    

这可能会被优化一点,但现在可以正常工作。我怀疑它可能会遇到需要修复的 GMT/BST 时钟更改问题......不是时钟更改问题都那么紧迫,嗯 Apple? ;P

之后只是响应tableview时使用映射数组的一种情况:

#pragma mark -
#pragma mark Table view data source

// Gets the booking from the fetchedResultsController using a remapped indexPath
- (Booking *)bookingForMappedIndexPath:(NSIndexPath *)indexPath 
    return (Booking *)[self.fetchedResultsController objectAtIndexPath:
                       [NSIndexPath indexPathForRow:indexPath.row inSection:frcMap[indexPath.section]]];


- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 
    return 7;   // 7 days viewed


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 

    // Rows in section or 1 if no section
    if (frcMap[section] != -1) 
        id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:frcMap[section]];
        return [sectionInfo numberOfObjects];
     else 
        return 1;
    



- (UITableViewCell *)tableView:(UITableView *)_tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 

    static NSString *CellIdentifier = @"RegularCell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) 
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
    

    // Configure the cell.
    [self configureCell:cell atIndexPath:indexPath];
    return cell;


- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath 

    // If no actual bookings for section then its a blank cell
    if (frcMap[indexPath.section] == -1) 

        // Configure a blank cell.
        cell.textLabel.text = @"No Bookings";
        cell.detailTextLabel.text = @"";

        cell.textLabel.font = [UIFont systemFontOfSize:16];
        cell.textLabel.textColor = [UIColor lightGrayColor];

        cell.accessoryType = UITableViewCellAccessoryNone;
        cell.selectionStyle = UITableViewCellSelectionStyleNone;

     else 

        // Regular cell
        Booking *booking = [self bookingForMappedIndexPath:indexPath];
        cell.textLabel.text = booking.desc;
        cell.detailTextLabel.text = [NSString stringWithFormat:@"%@ %@", booking.location, booking.detail];

        cell.textLabel.font = [UIFont systemFontOfSize:14];
        cell.textLabel.textColor = [UIColor darkTextColor];
        cell.detailTextLabel.font = [UIFont systemFontOfSize:12];

        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
        cell.selectionStyle = UITableViewCellSelectionStyleBlue;
    

非常欢迎任何 cmet 或更好的写作方式:)

【讨论】:

谢谢。在我正在开发的应用程序中,我遇到了完全相同的问题,试图在空白的部分显示“昨天、今天、明天”事件,并带有“今天没有”单元格。在我看到这个之前,我实际上采用了相同的解决方案。可惜 FetchedResultsController 对此没有任何支持...【参考方案2】:

我没用过这么多,但你可以看看 NSFetchedResultsSectionInfo 协议。显然可以这样使用:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 

NSInteger numberOfRows = 0; 
if ([[fetchedResultsController sections] count] > 0)
    
    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
    numberOfRows = [sectionInfo numberOfObjects];
    
return numberOfRows;

祝你好运。

【讨论】:

感谢您的回复。除非我遗漏了一些东西,否则我仍然需要做一个映射练习来找到合适的部分——即:tableView 会要求第 0 到 6 部分(例如,代表 11 月 1 日到 11 月 7 日),但 NSFetchedResultsController 可能只有第 0、1 和 2 部分(可能代表 11 月 2 日、11 月 3 日和 11 月 6 日的预订)。我可以在每次需要参考 Bookings 时进行此映射,或者在构造 NSFetchedResultsController 时缓存映射,但我希望有更好/更整洁的方法来做到这一点。 你是对的。很抱歉不能提供更多帮助。如果时间允许,我稍后会尝试这个。很想知道你是否/如何解决这个问题。 实际上,对此进行更多思考,我认为目前一个可用的解决方法是像往常一样创建一个 NSFetchedResultsController 但在最后设置一个“映射”数组的字段来翻译表格视图的部分到 NSFetchedResultsController 的部分。这应该意味着我可以从 resultsController 获取对象通知(这很好;) - 当通知通过 resultsController 添加/删除的项目时,我只需要更新“映射”数组。无论哪种方式,如果我找到更好的解决方案,我一定会在这里发布。 听起来很有希望。我期待看到您的解决方案。【参考方案3】:

我也有这个问题。我写了一个 NSFetchedResultsController 的子类来解决这个问题:

https://github.com/timothyarmes/TAFetchedResultsController

提姆

【讨论】:

感谢蒂姆的辛勤工作,但对您的解决方案的真实印象是,它不容易被放入现有项目中。创建一个新实体来支持这一点,并处理删除逻辑比我预期的“绕过这个小问题”要多得多

以上是关于向 NSFetchedResultsController 添加额外的部分的主要内容,如果未能解决你的问题,请参考以下文章

NSFetchedResultsController - 用户驱动的更改

前向散射光 与 后向散射光

砍树树的豁口向东,树最后向哪边倒?砍树树的豁口向东,树最后向哪边倒?

SPARK的计算向量化-已有的向量化项目

将数组向右而不是向左旋转

前向链接与后向链接