如何实现CoreData记录的重新排序?

Boo*_*oon 64 core-data ios

我在我的iPhone应用程序中使用CoreData,但CoreData没有提供允许您重新排序记录的自动方式.我想使用另一列来存储订单信息,但使用连续的数字来排序索引有一个问题.如果我处理大量数据,重新排序记录可能涉及更新订单信息上的大量记录(这有点像改变数组元素的顺序)

实施有效订购方案的最佳方法是什么?

Ale*_*cić 91

FetchedResultsController及其委托不应用于用户驱动的模型更改.请参阅Apple参考文档.查找用户驱动的更新部分.因此,如果你寻找一些神奇的,单行的方式,那就不可能了.

您需要做的是使用此方法进行更新:

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
 userDrivenDataModelChange = YES;

 ...[UPDATE THE MODEL then SAVE CONTEXT]...

 userDrivenDataModelChange = NO;
}
Run Code Online (Sandbox Code Playgroud)

并且还会阻止通知执行任何操作,因为用户已完成更改:

- (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;
 ...
}
Run Code Online (Sandbox Code Playgroud)

我刚刚在我的待办事项应用程序(Quickie)中实现了它,它工作正常.

  • 您可以将NSFetchedResultsController的委托设置为nil,而不是添加额外的布尔标志,同时应用更改,然后返回到self. (15认同)
  • 工作得很好,不要忘记做同样的 - (void)控制器:(NSFetchedResultsController*)控制器didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type如果你的表有section (3认同)

iwa*_*bed 14

下面是一个快速示例,显示了将获取的结果转储到NSMutableArray中的方法,该NSMutableArray用于移动单元格.然后,您只需更新所调用实体的属性orderInTable,然后保存托管对象上下文.

这样,您不必担心手动更改索引,而是让NSMutableArray为您处理.

创建一个可用于临时绕过的BOOL NSFetchedResultsControllerDelegate

@interface PlaylistViewController ()
{
    BOOL changingPlaylistOrder;
}
@end
Run Code Online (Sandbox Code Playgroud)

表视图委托方法:

- (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;
}
Run Code Online (Sandbox Code Playgroud)

您的代表现在看起来像这样:

- (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];
}
Run Code Online (Sandbox Code Playgroud)


dk.*_*dk. 8

迟到的回复:也许您可以将排序键存储为字符串.在两个现有行之间插入记录可以通过向字符串添加附加字符来简单地完成,例如在行"A"和"B"之间插入"AM".不需要重新排序.类似的想法可以通过在4字节整数上使用浮点数或一些简单的位运算来实现:插入一个行,其排序键值是相邻行之间的一半.

如果字符串太长,浮动太小,或者int中没有空间,可能会出现病态情况,但是你可以重新编号实体并重新开始.在极少数情况下扫描并更新所有记录比每次用户重新排序时对每个对象进行错误处理要好得多.

例如,考虑int32.使用高3字节作为初始排序可以为您提供近1700万行,并且能够在任意两行之间插入多达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
Run Code Online (Sandbox Code Playgroud)

通常你会调用AppendRow产生sortKeys为0x10000,0x20000,0x30000等的行.有时你需要InsertRow,比如说在第一个和第二个之间,导致sortKey为0x180000.


Ste*_*han 5

我已经使用double值实现了@andrew/@dk的方法.

你可以在github上找到UIOrderedTableView.

随意分叉吧:)


Ari*_*sky 5

我从Matt Gallagher的博客中找到了这个方法(找不到原始链接).如果您有数百万条记录,这可能不是最佳解决方案,但会延迟保存,直到用户完成对记录的重新排序.

- (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];
}
Run Code Online (Sandbox Code Playgroud)

  • 如果用户在一个Edit周期中移动多次,则allFRCObjects数组将在连续移动开始时返回其原始状态.因此,当用户单击"完成"时,只能保存最后一次移动. (2认同)