如何实现CoreDatalogging的重新sorting?
我为我的iPhone应用程序使用CoreData,但CoreData不提供允许您重新sortinglogging的自动方法。 我想用另一列来存储订单信息,但使用连续的数字sorting索引有一个问题。 如果我正在处理大量的数据,重新sortinglogging可能涉及更新订购信息上的很多logging(这就像改变数组元素的顺序)
实施高效订购scheme的最佳方式是什么?
FetchedResultsController及其委托并不意味着用于用户驱动的模型更改。 请参阅Apple参考文档 。 寻找用户驱动的更新部分。 所以,如果你寻找一些神奇的,单线的方式,可悲的是,没有这样的。
你需要做的是在这个方法中进行更新:
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { userDrivenDataModelChange = YES; ...[UPDATE THE MODEL then SAVE CONTEXT]... userDrivenDataModelChange = NO; }
并且还阻止通知做任何事情,因为用户已经做了改变:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { if (userDrivenDataModelChange) return; ... } - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { if (userDrivenDataModelChange) return; ... } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { if (userDrivenDataModelChange) return; ... }
我刚刚在我的待办事项应用程序(Quickie)中实现了这一点,它工作正常。
下面是一个简单的例子,展示了如何将获取的结果转储到用来移动单元格的NSMutableArray中。 然后,您只需更新名为orderInTable
的实体上的属性,然后保存托pipe的对象上下文。
这样,您不必担心手动更改索引,而是让NSMutableArray为您处理。
创build一个BOOL,你可以用它暂时绕过NSFetchedResultsControllerDelegate
@interface PlaylistViewController () { BOOL changingPlaylistOrder; } @end
表视图委托方法:
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath { // Refer to https://developer.apple.com/library/ios/documentation/CoreData/Reference/NSFetchedResultsControllerDelegate_Protocol/Reference/Reference.html#//apple_ref/doc/uid/TP40008228-CH1-SW14 // Bypass the delegates temporarily changingPlaylistOrder = YES; // Get a handle to the playlist we're moving NSMutableArray *sortedPlaylists = [NSMutableArray arrayWithArray:[self.fetchedResultsController fetchedObjects]]; // Get a handle to the call we're moving Playlist *playlistWeAreMoving = [sortedPlaylists objectAtIndex:sourceIndexPath.row]; // Remove the call from it's current position [sortedPlaylists removeObjectAtIndex:sourceIndexPath.row]; // Insert it at it's new position [sortedPlaylists insertObject:playlistWeAreMoving atIndex:destinationIndexPath.row]; // Update the order of them all according to their index in the mutable array [sortedPlaylists enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { Playlist *zePlaylist = (Playlist *)obj; zePlaylist.orderInTable = [NSNumber numberWithInt:idx]; }]; // Save the managed object context [commonContext save]; // Allow the delegates to work now changingPlaylistOrder = NO; }
你的代performance在看起来像这样:
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { if (changingPlaylistOrder) return; switch(type) { case NSFetchedResultsChangeMove: [self configureCell:(PlaylistCell *)[self.tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath]; break; } } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { if (changingPlaylistOrder) return; [self.tableView reloadData]; }
一个迟到的答复:也许你可以存储sorting键作为一个string。 在两个现有行之间插入一条logging可以通过向string添加一个额外字符来轻松完成,例如,在行“A”和“B”之间插入“AM”。 不需要重新sorting。 通过在一个4字节的整数上使用一个浮点数或一些简单的位算术可以实现一个类似的想法:插入一行,sorting键值是相邻行之间的一半。
如果线条太长,浮球太小,或者没有更多的空间,病态的情况可能会出现,但是你可以重新编号实体并重新开始。 在罕见场合扫描和更新所有logging要比每次用户重新sorting时错误logging每个对象要好得多。
例如,考虑int32。 使用高3字节作为初始sorting,可以为您提供将近1,700万行,并且可以在任意两行之间插入最多256行。 2个字节允许在重新扫描之前在任意两行之间插入65000行。
下面是我想到的用于插入2个字节和2个字节的伪代码:
AppendRow:item item.sortKey = tail.sortKey + 0x10000 InsertRow:item betweenRow:a andNextRow:b item.sortKey = a.sortKey + (b.sortKey - a.sortKey) >> 1
通常你会调用AppendRow导致sortKeys为0x10000,0x20000,0x30000等行。有时你将不得不插入第一个和第二个,导致sortKey 0x180000。
我用double值实现了@andrew / @dk的方法。
你可以在github上findUIOrderedTableView 。
随时叉起来:)
我从马特·加拉格尔的博客的方法改编(找不到原始链接)。 如果您拥有数百万条logging,这可能不是最好的解决scheme,但会延迟保存,直到用户完成对logging的重新sorting为止。
- (void)moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath sortProperty:(NSString*)sortProperty { NSMutableArray *allFRCObjects = [[self.frc fetchedObjects] mutableCopy]; // Grab the item we're moving. NSManagedObject *sourceObject = [self.frc objectAtIndexPath:sourceIndexPath]; // Remove the object we're moving from the array. [allFRCObjects removeObject:sourceObject]; // Now re-insert it at the destination. [allFRCObjects insertObject:sourceObject atIndex:[destinationIndexPath row]]; // All of the objects are now in their correct order. Update each // object's displayOrder field by iterating through the array. int i = 0; for (NSManagedObject *mo in allFRCObjects) { [mo setValue:[NSNumber numberWithInt:i++] forKey:sortProperty]; } //DO NOT SAVE THE MANAGED OBJECT CONTEXT YET } - (void)setEditing:(BOOL)editing { [super setEditing:editing]; if(!editing) [self.managedObjectContext save:nil]; }
实际上,有一个更简单的方法,使用“double”types作为sorting列。
然后,每当您重新订购时,您只需要重新设置重新sorting项目的订单属性的值:
reorderedItem.orderValue = previousElement.OrderValue + (next.orderValue - previousElement.OrderValue) / 2.0;
我终于放弃在编辑模式下的FetchController,因为我需要重新sorting我的表格单元格。 我想看看它的一个例子。 相反,我保留了一个mutablearray作为表的当前视图,并保持CoreData orderItem的一致性。
NSUInteger fromRow = [fromIndexPath row]; NSUInteger toRow = [toIndexPath row]; if (fromRow != toRow) { // array up to date id object = [[eventsArray objectAtIndex:fromRow] retain]; [eventsArray removeObjectAtIndex:fromRow]; [eventsArray insertObject:object atIndex:toRow]; [object release]; NSFetchRequest *fetchRequestFrom = [[NSFetchRequest alloc] init]; NSEntityDescription *entityFrom = [NSEntityDescription entityForName:@"Lister" inManagedObjectContext:managedObjectContext]; [fetchRequestFrom setEntity:entityFrom]; NSPredicate *predicate; if (fromRow < toRow) predicate = [NSPredicate predicateWithFormat:@"itemOrder >= %d AND itemOrder <= %d", fromRow, toRow]; else predicate = [NSPredicate predicateWithFormat:@"itemOrder <= %d AND itemOrder >= %d", fromRow, toRow]; [fetchRequestFrom setPredicate:predicate]; NSError *error; NSArray *fetchedObjectsFrom = [managedObjectContext executeFetchRequest:fetchRequestFrom error:&error]; [fetchRequestFrom release]; if (fetchedObjectsFrom != nil) { for ( Lister* lister in fetchedObjectsFrom ) { if ([[lister itemOrder] integerValue] == fromRow) { // the item that moved NSNumber *orderNumber = [[NSNumber alloc] initWithInteger:toRow]; [lister setItemOrder:orderNumber]; [orderNumber release]; } else { NSInteger orderNewInt; if (fromRow < toRow) { orderNewInt = [[lister itemOrder] integerValue] -1; } else { orderNewInt = [[lister itemOrder] integerValue] +1; } NSNumber *orderNumber = [[NSNumber alloc] initWithInteger:orderNewInt]; [lister setItemOrder:orderNumber]; [orderNumber release]; } } NSError *error; if (![managedObjectContext save:&error]) { NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); // Fail } } }
如果任何人有使用fetchController解决scheme请发布。
所以在这个问题上花了一些时间…!
上面的答案是伟大的积木,如果没有他们,我就会迷失方向,但是和其他答复一样,我发现他们只是部分工作。 如果你实现它们,你会发现他们工作了一两次,然后出错,或者你丢失了数据。 下面的答案远非完美 – 这是相当多的深夜,反复试验的结果。
这些方法有一些问题:
-
链接到NSMutableArray的NSFetchedResultsController不保证上下文将被更新,所以你可能会看到这有效,但不是其他人。
-
用于交换对象的复制然后删除方法也是难以预测的行为。 我在其他地方发现了引用在上下文中已经被删除的对象的不可预知的行为。
-
如果您使用对象索引行并有部分,那么这将无法正常工作。 上面的一些代码只使用.row属性,不幸的是这可能引用yt中的多行
-
使用NSFetchedResults Delegate = nil对于简单的应用程序来说是可以的,但是考虑到要使用委托来捕获将被复制到数据库的更改,那么您可以看到这将无法正常工作。
-
核心数据并不真正支持正确的SQL数据库的sorting和sorting。 上面的for循环解决scheme是好的,但真的应该有一个正确的方式来订购数据 – IOS8? – 所以你需要进入这个期望,你的数据将在各地。
人们针对这些post发布的问题涉及到很多这些问题。
我有一个简单的桌面应用程序部分“工作 – 仍然有我无法解释的UI行为,但我相信我已经到底了… …
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
这是通常的代表
{ userDrivenDataModelChange = YES;
使用如上所述的信号量机制和if()返回结构。
NSInteger sourceRow = sourceIndexPath.row; NSInteger sourceSection = sourceIndexPath.section; NSInteger destinationRow = destinationIndexPath.row; NSInteger destinationSection = destinationIndexPath.section;
并不是所有这些都在代码中使用,但有它们的debugging是有用的
NSError *error = nil; NSIndexPath *destinationDummy; int i = 0;
variables的最终初始化
destinationDummy = [NSIndexPath indexPathForRow:0 inSection:destinationSection] ; // there should always be a row zero in every section - although it's not shown
我在每个隐藏的部分中使用0行,这存储了部分名称。 这允许该部分可见,即使没有“活动logging”。 我使用0行来获取部分名称。 这里的代码有点不整洁,但它的工作。
NSManagedObjectContext *context = [self.fetchedResultsController managedObjectContext]; NSManagedObject *currentObject = [self.fetchedResultsController objectAtIndexPath:sourceIndexPath]; NSManagedObject *targetObject = [self.fetchedResultsController objectAtIndexPath:destinationDummy];
获取上下文以及源和目标对象
此代码然后创build一个新的对象,它将从源中获取数据,并从目标中获取该部分。
// set up a new object to be a copy of the old one NSManagedObject *newObject = [NSEntityDescription insertNewObjectForEntityForName:@"List" inManagedObjectContext:context]; NSString *destinationSectionText = [[targetObject valueForKey:@"section"] description]; [newObject setValue:destinationSectionText forKeyPath:@"section"]; [newObject setValue: [NSNumber numberWithInt:9999999] forKey:@"rowIndex"]; NSString *currentItem = [[currentObject valueForKey:@"item"] description]; [newObject setValue:currentItem forKeyPath:@"item"]; NSNumber *currentQuantity =[currentObject valueForKey:@"quantity"] ; [newObject setValue: currentQuantity forKey:@"rowIndex"];
现在创build一个新的对象,并保存上下文 – 这是作弊的移动操作 – 你可能不会得到新的logging在它被丢弃的地方 – 但至less它会在正确的部分。
// create a copy of the object for the new location [context insertObject:newObject]; [context deleteObject:currentObject]; if (![context save:&error]) { // Replace this implementation with code to handle the error appropriately. // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); }
现在执行上面所述的for循环更新。 请注意,在执行此操作之前,上下文已经保存 – 不知道为什么需要这样做,但是如果不是这样,它就无法正常工作!
i = 0; for (NSManagedObject *mo in [self.fetchedResultsController fetchedObjects] ) { [mo setValue:[NSNumber numberWithInt:i++] forKey:@"rowIndex"]; } if (![context save:&error]) { // Replace this implementation with code to handle the error appropriately. // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); }
设置信号量并更新表格
userDrivenDataModelChange = NO; [tableView reloadData];
}
这是我正在做的似乎工作。 对于每个实体,我都有一个createDate,用于在创build表时对表进行sorting。 它也是一个独特的关键。 所以在移动中,我所做的只是交换源和目标date。
我希望在执行saveContext之后,表格可以正确地sorting,但是会发生什么情况呢是两个单元格之间相互叠加。 所以我重新加载数据,订单被纠正。 从头开始的应用程序显示logging仍然在正确的顺序。
不知道这是一个通用的解决scheme,甚至是正确的,但到目前为止似乎工作。
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath { HomeEntity* source_home = [self getHomeEntityAtIndexPath:sourceIndexPath]; HomeEntity* destination_home = [self getHomeEntityAtIndexPath:destinationIndexPath]; NSTimeInterval temp = destination_home.createDate; destination_home.createDate = source_home.createDate; source_home.createDate = temp; CoreDataStack * stack = [CoreDataStack defaultStack]; [stack saveContext]; [self.tableView reloadData]; }
试试看看这里的iPhone核心数据教程。 其中一个部分讨论sorting(使用NSSortDescriptor)。
你也可以find核心数据基础页面是有用的。
- 如何处理关于核心数据,共享首选项和通知的Mac OS X Helper / Main应用程序体系结构?
- 核心数据迁移示例或说明多个通行证?
- Xcode 6 iPhone模拟器应用程序支持位置
- 核心数据主键
- 对多对多关系进行NSPredicate不区分大小写的匹配
- 如何只读取核心数据中的第一条logging?
- 核心数据multithreading应用程序
- 核心数据保存错误(NSValidationErrorKey,Cocoa错误1570)保存NSDate
- 如何使用UISearchDisplayController / UISearchBar过滤NSFetchedResultsController(CoreData)