NSFetchedresultscontroller 与 tableview 插入新部分崩溃
Posted
技术标签:
【中文标题】NSFetchedresultscontroller 与 tableview 插入新部分崩溃【英文标题】:NSFetchedresultscontroller with tableview inserting new section crash 【发布时间】: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];
> 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。我应该吗? 【参考方案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 的代码,某些方法可能被称为不同
【讨论】:
以上是关于NSFetchedresultscontroller 与 tableview 插入新部分崩溃的主要内容,如果未能解决你的问题,请参考以下文章