使用 NSFetchedResultsController 时出现奇怪的标题错误

Posted

技术标签:

【中文标题】使用 NSFetchedResultsController 时出现奇怪的标题错误【英文标题】:Weird Header Error when using NSFetchedResultsController 【发布时间】:2013-07-27 01:07:16 【问题描述】:

由于我无法真正描述此错误,因此我在屏幕上记录了该错误。 http://www.youtube.com/watch?v=w2FqKKcL2Ck&feature=youtu.be 基本上我不知道该怎么做。当用户完成“要完成的任务”部分中的所有任务时,我希望部分标题保留在那里......只是其中没有对象(只是因为它需要添加更多任务)。

幸运的是,当用户没有任何已完成的任务时,那里的标题会消失,这是应该发生的。我希望顶部标题不会消失...

同样,删除也是如此。当最后一个对象被删除时,我真的不希望“要完成的任务”部分完全消失。

我听说这是不可能的,除非您使用像 TAFetchResultsController 这样的自定义子类,但我尝试使用它,但它对我来说太复杂了,无法实现(并且有点破坏了我的应用程序而不是修复它)。也许你们有什么建议?

以下是一些相关代码:

任务核心数据属性

@interface Tasks : NSManagedObject
@property (nonatomic, retain) NSString *sectionString;
@end

@implementation Tasks
@dynamic sectionString;
@end

向不同部分添加任务

NSManagedObjectContext *context = self.managedObjectContext;
    NSManagedObject *startingTask = [NSEntityDescription insertNewObjectForEntityForName:@"Tasks" inManagedObjectContext:context];
    [startingTask setValue:@"Eat Dinner" forKey:@"taskName"];
    [startingTask setValue:[NSNumber numberWithDouble:400] forKey:@"timeInterval"];
    [startingTask setValue:@"Tasks To Complete" forKey:@"sectionString"];

     NSManagedObject *finishedTask = [NSEntityDescription insertNewObjectForEntityForName:@"Tasks" inManagedObjectContext:context];
    [finishedTask setValue:@"Do Laundry" forKey:@"taskName"];
    [finishedTask setValue:[NSNumber numberWithDouble:400] forKey:@"timeInterval"];
    [finishedTask setValue:@"Completed Tasks" forKey:@"sectionString"];

    NSError *error;
    if (![context save:&error]) 
        NSLog(@"couldn't save: %@", [error localizedDescription]);
     

TableViewController.m:

-(void) viewDidLoad

        // ---Start Core Data With NSFetchedResultsController---
[super viewDidLoad];
NSError *error;
if (![[self fetchedResultsController] performFetch:&error])
    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
    exit(-1);

// ---End Core Data w/ NSFetchedResultsController---

[self.tableView setDelegate:self];
[self setModalTransitionStyle:UIModalTransitionStyleCrossDissolve];
holdViewsArray = [[NSMutableArray alloc]init];

UIView *seperatorView;
UIView *seperatorView2;

NSString *sectionTitle = @"Tasks To Complete";
NSString *section2Title = @"Completed Tasks";
UILabel *label = [[UILabel alloc]init];
UILabel *label2 = [[UILabel alloc]init];

label.frame = CGRectMake(10.0, 5.0, 320.0, 50.0);
label.text = sectionTitle;

label2.frame = CGRectMake(10.0, 0.0, 320.0, 40.0);
label2.text = section2Title;

headerView = [[UIView alloc]initWithFrame:label.frame];
headerView2 = [[UIView alloc]initWithFrame:label2.frame];

CGRect sepFrame = CGRectMake(0, headerView.frame.size.height-2, 320, 1);
CGRect sep2Frame =CGRectMake(0, headerView2.frame.size.height-2, 320, 1);

seperatorView = [[UIView alloc] initWithFrame:sepFrame];
seperatorView2 = [[UIView alloc]initWithFrame:sep2Frame];

[headerView addSubview:seperatorView];
[headerView2 addSubview:seperatorView2];
[headerView addSubview:label];
[headerView addSubview:button];
[headerView2 addSubview:label2];
[holdViewsArray addObject:headerView];
[holdViewsArray addObject:headerView2];
    - (NSFetchedResultsController *)fetchedResultsController 

        if (_fetchedResultsController != nil) 
            return _fetchedResultsController;
        

        NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
        NSEntityDescription *entity = [NSEntityDescription
                                       entityForName:@"Tasks" inManagedObjectContext:managedObjectContext];
        [fetchRequest setEntity:entity];

        NSSortDescriptor *isCompleted = [[NSSortDescriptor alloc]initWithKey:@"sectionString" ascending:NO];
        NSSortDescriptor *sort = [[NSSortDescriptor alloc]
                                  initWithKey:@"dateCreated" ascending:YES];
        [fetchRequest setSortDescriptors:@[isCompleted, sort]];
        [fetchRequest setFetchBatchSize:20];

        NSFetchedResultsController *theFetchedResultsController =
        [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                            managedObjectContext:managedObjectContext sectionNameKeyPath:@"sectionString"
                                                       cacheName:nil];
        self.fetchedResultsController = theFetchedResultsController;
        _fetchedResultsController.delegate = self;

        return _fetchedResultsController;

    -(void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath 
        Tasks *task = [_fetchedResultsController objectAtIndexPath:indexPath];
        cell.textLabel.text = task.taskName.uppercaseString;
        cell.detailTextLabel.text = [NSString stringWithFormat:@"%.0f", task.timeInterval];
        cell.imageView.image = [UIImage imageNamed:@"unchecked.png"];
        cell.imageView.highlightedImage = [UIImage imageNamed:@"uncheckedhighlighted.png"];
        [cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
        if (indexPath.section == 1)
        [cell.contentView setAlpha:0.5];
        else 
        [cell.contentView setAlpha:1];
        

        UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(handlechecking:)];
        [cell.imageView addGestureRecognizer:tap];
        cell.imageView.userInteractionEnabled = YES;
    
    -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
     cellSubclassCell *cell = [tableView dequeueReusableCellWithIdentifier:@"UITableViewCell"];
        if (!cell)
            cell = [[cellSubclassCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:@"UITableViewCell"];
        [self configureCell:cell atIndexPath:indexPath];
        return cell;
        
    -(void)handlechecking:(UITapGestureRecognizer *)t
            CGPoint tapLocation = [t locationInView:self.tableView];
            NSIndexPath *tappedIndexPath = [self.tableView indexPathForRowAtPoint:tapLocation];
            Tasks *task = [_fetchedResultsController objectAtIndexPath:tappedIndexPath];
        if ([task.sectionString isEqual: @"Tasks To Complete"])
            task.sectionString = @"Completed Tasks";
         else if ([task.sectionString isEqualToString:@"Completed Tasks"])
        task.sectionString = @"Tasks To Complete";
        
        [self.tableView reloadData];
        NSTimeInterval time = [[NSDate date] timeIntervalSinceReferenceDate];
        [task setDateCreated:time];
    
-(UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section

    switch (section) 
    case 0:
            return [holdViewsArray objectAtIndex:0];
            break;
    case 1:
            return [holdViewsArray objectAtIndex:1];
            break;

    
    return 0;

【问题讨论】:

【参考方案1】:

NSFetchedResultsController 从 fetch 返回的对象中推断出部分。因此,如果某个部分的项目数变为零,则该部分将消失。这就是NSFetchedResultsController 的工作原理。

您最简单的选择可能是第 3 方框架。看看TLIndexPathTools。它提供了NSFetchedResultsController 的替代方案,不需要Core Data 或NSFetchRequest,这意味着您可以在更多场景中使用它。看看一些示例项目(Core Data 的一个叫做"Core Data")——用几行代码就可以完成一些很酷的事情。

TLIndexPathTools 支持空部分,但不是以您需要的方式明确显示。但是,您可以通过覆盖TLIndexPathController 用几行代码来完成它。代码看起来像这样:

@interface MyIndexPathController : TLIndexPathController
@end

#import "MyIndexPathController.h"
@implementation MyIndexPathController    

- (void)setDataModel:(TLIndexPathDataModel *)dataModel

    if ([dataModel sectionForSectionName:@"MyFirstSectionName"] == NSNotFound) 
        TLIndexPathSectionInfo *firstSection = [[TLIndexPathSectionInfo alloc] initWithItems:nil andName:@"MyFirstSectionName"];
        NSMutableArray *sectionInfos = [NSMutableArray arrayWithArray:dataModel.sections];
        [sectionInfos insertObject:firstSection atIndex:0];
        dataModel = [[TLIndexPathDataModel alloc] initWithSectionInfos:sectionInfos andIdentifierKeyPath:dataModel.identifierKeyPath andCellIdentifierKeyPath:dataModel.cellIdentifierKeyPath];
    
    super.dataModel = dataModel;


@end

这里发生的是我们拦截控制器生成的数据模型(基于我们的获取请求的结果)并检查感兴趣的部分是否存在。如果没有,我们创建一个空的节信息对象,并将其与其他节信息组合成一个新的数据模型,并将其传递给超类。

如果需要,一般扩展以使用任何一组必需的部分应该不会太难。

【讨论】:

我尝试实现这个框架,但它对我来说太混乱了......你的示例代码也导致了 setDataModel 的断点错误:/ 如果您提出具体问题,我可以解释。上面的代码是一个模型。如果您分享一些有关您的实施和错误的详细信息,我可以提供帮助。

以上是关于使用 NSFetchedResultsController 时出现奇怪的标题错误的主要内容,如果未能解决你的问题,请参考以下文章

核心数据保存竞争条件错误

在 Swift 3 中难以配置 NSFetchedResultsController

为啥 beginUpdates/endUpdates 会重置表视图位置以及如何阻止它这样做?

测试使用

第一篇 用于测试使用

在使用加载数据流步骤的猪中,使用(使用 PigStorage)和不使用它有啥区别?