【问题标题】:NSFetchedresultscontroller with tableview inserting new section crashNSFetchedresultscontroller 与 tableview 插入新部分崩溃
【发布时间】:2014-05-30 16:36:51
【问题描述】:

我正在开发消息应用程序以及我保存在核心数据中的所有消息。我使用 NSFetchedResultsController 和 tableView 在屏幕上显示它们。我的 nsfetchedResultsController 中有部分是星期几。我的意思是一个部分是例如今天,另一个部分是例如昨天。当我想添加应该插入到新部分的新消息时,当我尝试将新对象插入到在 tableview 中生成新部分的核心数据时,我遇到了崩溃。这是我得到的日志:

无效更新:第 0 节中的行数无效。更新后现有节中包含的行数 (12) 必须等于更新前该节中包含的行数 (8),加上或减去从该节插入或删除的行数(1 插入,1 删除)加上或减去移入或移出该节的行数(0 移入,0 移出)

这是我的 NSFETCHRESULTSCONTROLLER DELEGATE 代码

- (void)dataLoaderDidChangeContent:(GISingleConversationDataLoader *)controller {
    [self.tableView endUpdates];

    if (_shouldScrollToBottomAfterUpdate) {
        [self scrollToBottomAnimated:NO];
    }
}

- (void)dataLoaderWillChangeContent:(GISingleConversationDataLoader *)controller {
    [self.tableView beginUpdates];
}

- (void)dataLoader:(GISingleConversationDataLoader *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
    UITableView *tableView = self.tableView;

    switch(type) {

        case NSFetchedResultsChangeInsert:
            if ([self.tableView numberOfSections]) {
                [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                                 withRowAnimation:UITableViewRowAnimationBottom];
            } else {
                [tableView insertSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];
            }
            _shouldScrollToBottomAfterUpdate = YES;
            break;

        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate:
            [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                                 withRowAnimation:UITableViewRowAnimationNone];
            break;

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationNone];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationNone];
            break;
    }
}

数据来源:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [self.dataProvider.fetchedResultsController.sections count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if ([[self.dataProvider.fetchedResultsController sections] count] < 1) {
        return 0;
    }
    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.dataProvider.fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];
}

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

    GICDChatMessage *message = [self _messageAtIndexPath:indexPath];

    GIChatMessageType type = [message.type integerValue];

    NSString *kMessageTypeKey = [message isOutgoing] ? kOutgoingMessageKey : kIncomingMessageKey;
    NSString *CellIdentifier;  CellIdentifier = self.cellIdentifiers[@(type)][kMessageTypeKey];

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
    self.configureCellBlock(cell, message, indexPath);

    [cell setSelectionStyle:UITableViewCellSelectionStyleNone];
    [cell setBackgroundColor:tableView.backgroundColor];

    return cell;
}

我创建 NSFetchedResultsController 并在我的数据加载器中管理他。我创建它是为了让我的 viewController 更轻。

这是我的数据加载器实现:

- (id)initWithConversation:(GIConversation *)conversation delegate:(id<GISingleConversationDataLoaderProtocol>)delegate {
    self = [super init];
    if (self) {
        _conversation = conversation;
        _delegate = delegate;
        [self resultsController];
        [_fetchedResultsController setDelegate:self];
        [self performFetch];
    }
    return self;
}

- (NSPredicate *)predicate {
    return [NSPredicate predicateWithFormat:@"conversationJid == %@", _conversation.jid];
}

- (NSString *)entityName {
    return @"GICDChatMessage";
}

- (NSArray *)sortDescriptors {
    return @[[NSSortDescriptor sortDescriptorWithKey:@"receivedAt" ascending:YES]];
}

- (NSFetchedResultsController*)resultsController {
    if (_fetchedResultsController == nil) {
        _fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:[self fetchRequest] managedObjectContext:[self context] sectionNameKeyPath:@"sectionNameKeyPath" cacheName:nil];
    }
    return _fetchedResultsController;
}

-(void)performFetch {
    if (_fetchedResultsController) {
        [_fetchedResultsController performFetch:nil];
        [_delegate dataLoaderDidChangeContent:self];
    }
}


- (NSFetchRequest*)fetchRequest {

    NSFetchRequest *result = [NSFetchRequest fetchRequestWithEntityName:[self entityName]];

    NSPredicate *mainPredicate = [self predicate];
    result.predicate = mainPredicate;
    result.sortDescriptors = [self sortDescriptors];

    return result;
}
- (NSManagedObjectContext *)context {
    return [[GICoreDataManager sharedInstance] mainContext];
}

#pragma mark - fetch change

- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath {

    [_delegate dataLoader:self
          didChangeObject:anObject
              atIndexPath:indexPath
            forChangeType:type
             newIndexPath:newIndexPath];
}

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    [_delegate dataLoaderWillChangeContent:self];
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    [_delegate dataLoaderDidChangeContent:self];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {

    [_delegate dataLoader:self didChangeSection:sectionInfo atIndex:sectionIndex forChangeType:type];
}

&gt; sectionNameKeyPath 是在我的模型类类别中定义的键,看起来::

- (NSString *)sectionNameKeyPath {

    NSTimeInterval timeInterval = [self.receivedAt timeIntervalSinceNow];
    NSString *string = [[GICDChatMessage timeIntervalFormatter] stringForTimeInterval:roundf(timeInterval / oneDayTimeInterval) * oneDayTimeInterval];

    if ([string isEqualToString:NSLocalizedString(@"just now", nil)]) {
        string = NSLocalizedString(@"Today", nil);
    }

    return string;
}

它只是对每天的消息进行排序。

你知道是什么导致了这个问题或我做错了什么吗?

我的英语不好,所以如果你不明白我的意思,请问我,我会尽量解释得更清楚。

【问题讨论】:

  • 我们还能看到tableview的数据源方法吗?我怀疑它与获取的结果控制器不同步。
  • 由于这些方法几乎是直接来自参考的样板(它们应该是,没有理由真正改变它们),dataSource 方法是下一个可能的候选方法。
  • NSFetchedResultsChangeInsert 案例不是样板代码,有什么理由吗?那么controller:didChangeSection:委托方法呢,是不是也实现了?
  • 这是我的数据源 :) 不,我没有实现 DidChangeSection。我应该吗?

标签: ios objective-c uitableview nsfetchedresultscontroller


【解决方案1】:

你需要实现 didChangeSection:

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
    atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {

    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                            withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                             withRowAnimation:UITableViewRowAnimationFade];
            break;
    }
}

编辑你也需要删除你管理部分的代码部分:

   if ([self.tableView numberOfSections]) {
        [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                         withRowAnimation:UITableViewRowAnimationBottom];
    } else {
        [tableView insertSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone];
    }

【讨论】:

  • 我添加了 didChangeSection 但我仍然有同样的问题:/
  • 我添加了 DidChangeSection 和 Yes 部分的问题已解决,但现在它停在这里[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationBottom]; 并且它不想更进一步:/
  • 您是否摆脱了执行此操作的部分: if ([self.tableView numberOfSections]) { [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationBottom]; } else { [tableView insertSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationNone]; } ?
  • 我删除了它,我有同样的问题,但现在有行
  • 这是样板的 NSFetchedResultsControllerDelegate 代码,你可以在这里找到整个代码:developer.apple.com/library/ios/documentation/CoreData/…
【解决方案2】:

您还没有实现NSFetchedResultsControllerDelegate 协议的任何方法。相反,您编写了具有相似名称的自定义方法。例如,以下是您发布的方法:

- (void)dataLoaderWillChangeContent:(GISingleConversationDataLoader *)controller {
    [self.tableView beginUpdates];
}

虽然上述方法的名称类似于NSFetchedResultsControllerDelegate 协议的方法,但它不是同一个方法,因此您的获取结果控制器永远不会调用。如果您希望获取的结果控制器调用它,则需要实现以下方法的声明:

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller;

同样,您应该更改该部分代码中其他方法的名称和签名,以匹配 Core Data 提供的实际 API。

【讨论】:

  • 这些方法在我的dataProvider中实现
  • @user3533403 如果是这种情况,您需要编辑您的问题以更准确地描述您的实现,包括实际上实现NSFetchedResultsControllerDelegate 协议的方法。你还应该解释你的“数据加载器”的设计,不管是什么。
【解决方案3】:

汇总更新

然后在controllerDidChangeContent你需要处理所有收到的更新并转发到UITableView

这段代码非常适合我:

class SomeVC: UITableViewController {

        /* ... */

// Add properties for each expected update type

    var insertedSectionIndexes = NSMutableIndexSet()
    var deletedSectionIndexes  = NSMutableIndexSet()

    var deletedRowIndexPaths:  Set<NSIndexPath> = []
    var insertedRowIndexPaths: Set<NSIndexPath> = []
    var updatedRowIndexPaths:  Set<NSIndexPath> = []
}



extension SomeVC: NSFetchedResultsControllerDelegate {

    func controllerWillChangeContent (controller: NSFetchedResultsController) {}

    func controller(controller: NSFetchedResultsController,
        didChangeObject anObject: NSManagedObject,
        atIndexPath indexPath: NSIndexPath?,
        forChangeType type: NSFetchedResultsChangeType,
        newIndexPath: NSIndexPath?) {
            switch type {
            case .Insert: insertedRowIndexPaths.insert(indexPath!)
            case .Update: updatedRowIndexPaths.insert(indexPath!)
            case .Move:
                if newIndexPath?.section != indexPath?.section {
                    insertedRowIndexPaths.insert(newIndexPath!)
                    deletedRowIndexPaths.insert(indexPath!)
                }
            case .Delete: deletedRowIndexPaths.insert(indexPath!)
            }
    }

    func controller(controller: NSFetchedResultsController,
        didChangeSection sectionInfo: NSFetchedResultsSectionInfo,
        atIndex sectionIndex: Int,
        forChangeType type: NSFetchedResultsChangeType) {
        switch type {
        case .Insert: insertedSectionIndexes.addIndex(sectionIndex)
        case .Delete: deletedSectionIndexes.addIndex(sectionIndex)
        default:()
        }
    }

    func controllerDidChangeContent (controller: NSFetchedResultsController) {

        tableView?.beginUpdates()

        tableView?.deleteSections(deletedSectionIndexes, withRowAnimation: .None)
        tableView?.insertSections(insertedSectionIndexes, withRowAnimation: .None)

        updatedRowIndexPaths.subtractInPlace(deletedRowIndexPaths)
        updatedRowIndexPaths.subtractInPlace(insertedRowIndexPaths)

        tableView?.deleteRowsAtIndexPaths(Array(deletedRowIndexPaths), withRowAnimation: .Automatic)
        tableView?.insertRowsAtIndexPaths(Array(insertedRowIndexPaths), withRowAnimation: .Automatic)
        tableView?.reloadRowsAtIndexPaths(Array(updatedRowIndexPaths), withRowAnimation: .Automatic)

        tableView?.endUpdates()
        // Do not forget to clean up!
        insertedSectionIndexes.removeAllIndexes()
        deletedSectionIndexes.removeAllIndexes()
        deletedRowIndexPaths = []
        insertedRowIndexPaths = []
        updatedRowIndexPaths = []
    }

}

注意:来自 Xcode 7 beta 2 的代码,某些方法可能被称为不同

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-09-08
    • 2011-01-29
    • 1970-01-01
    • 1970-01-01
    • 2010-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多