使用核心数据重新排列UITableView

Gre*_*mbs 23 iphone core-data uitableview

可能重复:
如何实现CoreData记录的重新排序?

我正在尝试找到一个代码示例,该示例演示如何在单元格使用fetchedResultsController(即与Core Data结合使用)时处理tableView中的移动/重新排列单元格.我正在获取moveRowAtIndexPath:调用我的数据源,但我无法找到正确的黑魔法组合,以使表/数据正确识别更改.

例如,当我将第0行移动到第2行然后放开时,它"看起来"正确.然后我点击"完成".向上滑动以填充第0行的行(1)仍然具有编辑模式外观(减去和移动图标),而下面的其他行滑回到正常外观.如果我然后向下滚动,因为第2行(原来是0,记得?)接近顶部,它会完全消失.

WTF.我是否需要以某种方式使fetchedResultsController无效?每当我将它设置为零时,我都会崩溃.我应该发布它吗?我在杂草里吗?

这是我目前在那里得到的......

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {

    NSManagedObjectContext *context = [fetchedResultsController managedObjectContext];

    /*
     Update the links data in response to the move.
     Update the display order indexes within the range of the move.
     */

    if (fromIndexPath.section == toIndexPath.section) {

        NSInteger start = fromIndexPath.row;
        NSInteger end = toIndexPath.row;
        NSInteger i = 0;
        if (toIndexPath.row < start)
            start = toIndexPath.row;
        if (fromIndexPath.row > end)
            end = fromIndexPath.row;
        for (i = start; i <= end; i++) {
            NSIndexPath *tempPath = [NSIndexPath indexPathForRow:i inSection:toIndexPath.section];
            LinkObj *link = [fetchedResultsController objectAtIndexPath:tempPath];
            //[managedObjectContext deleteObject:[fetchedResultsController objectAtIndexPath:tempPath]];
            link.order = [NSNumber numberWithInteger:i];
            [managedObjectContext refreshObject:link mergeChanges:YES];
            //[managedObjectContext insertObject:link];
        }

    }
    // Save the context.
    NSError *error;
    if (![context save:&error]) {
        // Handle the error...
    }

}

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

    // The fetch controller is about to start sending change notifications, so prepare the table view for updates.
    if (self.theTableView != nil)
        [self.theTableView beginUpdates];
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    // The fetch controller has sent all current change notifications, so tell the table view to process all updates.
    if (self.theTableView != nil) {
        [self.theTableView endUpdates];
    }
}
Run Code Online (Sandbox Code Playgroud)

Lou*_*arg 8

通常,当您看到正在发生的事件时,UI已经动画到新位置并告诉您相关信息,那么您对模型所做的更新无法正确反映导致下次出现故障的状态视图必须引用更新的模型.

我想你并不完全明白你应该在这个方法中做些什么.它被调用是因为UI已经改变,它需要让模型相应地改变.下面的代码假定结果已经在新订单中,您只需要重置订单字段,原因如下:

    for (i = start; i <= end; i++) {
            NSIndexPath *tempPath = [NSIndexPath indexPathForRow:i inSection:toIndexPath.section];
            LinkObj *link = [fetchedResultsController objectAtIndexPath:tempPath];
            //[managedObjectContext deleteObject:[fetchedResultsController objectAtIndexPath:tempPath]];
            link.order = [NSNumber numberWithInteger:i];
            [managedObjectContext refreshObject:link mergeChanges:YES];
            //[managedObjectContext insertObject:link];
    }
Run Code Online (Sandbox Code Playgroud)

问题在于您实际上并未更改基础模型中的顺序.这些indexPath来自UITableViewController,它告诉你用户在那些之间拖动到点,你需要根据更新底层数据.但是fetchedResultsController始终按排序顺序排列,因此在您更改这些属性之前,没有任何内容移动过.

问题是,它们没有被移动,你被叫来告诉你需要移动它们(通过调整可排序属性).你真的需要更像的东西:

NSNumber *targetOrder = [fetchedResultsController objectAtIndexPath:toIndexPath];
LinkObj *link = [fetchedResultsController objectAtIndexPath:FromPath];
link.order = targetOrder;
Run Code Online (Sandbox Code Playgroud)

这将导致对象重新排序,然后通过并清理应该向上移动的其他对象的任何订单号,意识到索引可能已经移动.


Gre*_*mbs 7

以下是正式工作的内容,包括删除,移动和插入.只要有影响订单的编辑操作,我就会"验证"订单.

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    if (indexPath.section != kHeaderSection) {

        if (editingStyle == UITableViewCellEditingStyleDelete) {

            @try {
                LinkObj * link = [self.fetchedResultsController objectAtIndexPath:indexPath];

                debug_NSLog(@"Deleting at indexPath %@", [indexPath description]);
            //debug_NSLog(@"Deleting object %@", [link description]);

                if ([self numberOfBodyLinks] > 1) 
                    [self.managedObjectContext deleteObject:link];

            }
            @catch (NSException * e) {
                debug_NSLog(@"Failure in commitEditingStyle, name=%@ reason=%@", e.name, e.reason);
            }

        }
        else if (editingStyle == UITableViewCellEditingStyleInsert) {
            // we need this for when they click the "+" icon; just select the row
            [theTableView.delegate tableView:tableView didSelectRowAtIndexPath:indexPath];
        }
    }
}

- (BOOL)validateLinkOrders {        
    NSUInteger index = 0;
    @try {      
        NSArray * fetchedObjects = [self.fetchedResultsController fetchedObjects];

        if (fetchedObjects == nil)
            return NO;

        LinkObj * link = nil;       
        for (link in fetchedObjects) {
            if (link.section.intValue == kBodySection) {
                if (link.order.intValue != index) {
                    debug_NSLog(@"Info: Order out of sync, order=%@ expected=%d", link.order, index);

                    link.order = [NSNumber numberWithInt:index];
                }
                index++;
            }
        }
    }
    @catch (NSException * e) {
        debug_NSLog(@"Failure in validateLinkOrders, name=%@ reason=%@", e.name, e.reason);
    }
    return (index > 0 ? YES : NO);
}


- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
    NSArray * fetchedObjects = [self.fetchedResultsController fetchedObjects];  
    if (fetchedObjects == nil)
        return;

    NSUInteger fromRow = fromIndexPath.row + NUM_HEADER_SECTION_ROWS;
    NSUInteger toRow = toIndexPath.row + NUM_HEADER_SECTION_ROWS;

    NSInteger start = fromRow;
    NSInteger end = toRow;
    NSInteger i = 0;
    LinkObj *link = nil;

    if (toRow < start)
        start = toRow;
    if (fromRow > end)
        end = fromRow;

    @try {

        for (i = start; i <= end; i++) {
            link = [fetchedObjects objectAtIndex:i]; //
            //debug_NSLog(@"Before: %@", link);

            if (i == fromRow)   // it's our initial cell, just set it to our final destination
                link.order = [NSNumber numberWithInt:(toRow-NUM_HEADER_SECTION_ROWS)];
            else if (fromRow < toRow)
                link.order = [NSNumber numberWithInt:(i-1-NUM_HEADER_SECTION_ROWS)];        // it moved forward, shift back
            else // if (fromIndexPath.row > toIndexPath.row)
                link.order = [NSNumber numberWithInt:(i+1-NUM_HEADER_SECTION_ROWS)];        // it moved backward, shift forward
            //debug_NSLog(@"After: %@", link);
        }
    }
    @catch (NSException * e) {
        debug_NSLog(@"Failure in moveRowAtIndexPath, name=%@ reason=%@", e.name, e.reason);
    }
}


- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {    
    @try {
        switch (type) {
            case NSFetchedResultsChangeInsert:
                [theTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
                [self validateLinkOrders];
                break;
            case NSFetchedResultsChangeUpdate:
                break;
            case NSFetchedResultsChangeMove:
                self.moving = YES;
                [self validateLinkOrders];
                break;
            case NSFetchedResultsChangeDelete:
                [theTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
                [self validateLinkOrders];
                break;
            default:
                break;
        }
    }
    @catch (NSException * e) {
        debug_NSLog(@"Failure in didChangeObject, name=%@ reason=%@", e.name, e.reason);
    }
}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
    switch(type) {
        case NSFetchedResultsChangeInsert:
            [self.theTableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade];
            break;

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

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    // The fetch controller has sent all current change notifications, so tell the table view to process all updates.
    @try {
        if (self.theTableView != nil) {
            //[self.theTableView endUpdates];
            if (self.moving) {
                self.moving = NO;
                [self.theTableView reloadData];
                //[self performSelector:@selector(reloadData) withObject:nil afterDelay:0.02];
            }
            [self performSelector:@selector(save) withObject:nil afterDelay:0.02];
        }   

    }
    @catch (NSException * e) {
        debug_NSLog(@"Failure in controllerDidChangeContent, name=%@ reason=%@", e.name, e.reason);
    }
}
Run Code Online (Sandbox Code Playgroud)


Tom*_*Tom 5

在表视图中移动行时,实际上同时将其他行(由至少一行组成)的块移动到另一个方向.诀窍是只更新此块和移动项的displayOrder属性.

首先,确保根据表当前显示顺序设置所有行的displayOrder属性.我们不必在这里保存上下文,我们将在实际移动操作完成后保存它:

- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
    [super setEditing:editing animated:animated];
    [_tableView setEditing:editing animated:animated];
    if(editing) {
        NSInteger rowsInSection = [self tableView:_tableView numberOfRowsInSection:0];
       // Update the position of all items
       for (NSInteger i=0; i<rowsInSection; i++) {
          NSIndexPath *curIndexPath = [NSIndexPath indexPathForRow:i inSection:0];
          SomeManagedObject *curObj = [_fetchedResultsController objectAtIndexPath:curIndexPath];
          NSNumber *newPosition = [NSNumber numberWithInteger:i];
          if (![curObj.displayOrder isEqualToNumber:newPosition]) {
             curObj.displayOrder = newPosition;
          }
       }
    }
}
Run Code Online (Sandbox Code Playgroud)

那么你唯一要做的就是更新移动项的位置以及fromIndexPath和toIndexPath之间的所有项的位置:

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
    NSInteger moveDirection = 1;
    NSIndexPath *lowerIndexPath = toIndexPath;
    NSIndexPath *higherIndexPath = fromIndexPath;
    if (fromIndexPath.row < toIndexPath.row) {
        // Move items one position upwards
        moveDirection = -1;
        lowerIndexPath = fromIndexPath;
        higherIndexPath = toIndexPath;
    }

    // Move all items between fromIndexPath and toIndexPath upwards or downwards by one position
    for (NSInteger i=lowerIndexPath.row; i<=higherIndexPath.row; i++) {
        NSIndexPath *curIndexPath = [NSIndexPath indexPathForRow:i inSection:fromIndexPath.section];
        SomeManagedObject *curObj = [_fetchedResultsController objectAtIndexPath:curIndexPath];
        NSNumber *newPosition = [NSNumber numberWithInteger:i+moveDirection];
        curObj.displayOrder = newPosition;
    }

    SomeManagedObject *movedObj = [_fetchedResultsController objectAtIndexPath:fromIndexPath];
    movedObj.displayOrder = [NSNumber numberWithInteger:toIndexPath.row];
    NSError *error;
    if (![_fetchedResultsController.managedObjectContext save:&error]) {
        NSLog(@"Could not save context: %@", error);
    }
}
Run Code Online (Sandbox Code Playgroud)


Jos*_*phH 5

最好的答案实际上是克林特哈里斯对这个问题的评论:

http://www.cimgf.com/2010/06/05/re-ordering-nsfetchedresultscontroller

要快速总结,关键部分是在您尝试重新排列的对象上具有displayOrder属性,并使用该字段上获取的结果控制器排序的排序描述.然后代码moveRowAtIndexPath:toIndexPath:如下所示:

- (void)tableView:(UITableView *)tableView 
moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath 
      toIndexPath:(NSIndexPath *)destinationIndexPath;
{  
  NSMutableArray *things = [[fetchedResultsController fetchedObjects] mutableCopy];

  // Grab the item we're moving.
  NSManagedObject *thing = [[self fetchedResultsController] objectAtIndexPath:sourceIndexPath];

  // Remove the object we're moving from the array.
  [things removeObject:thing];
  // Now re-insert it at the destination.
  [things insertObject:thing 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 things)
  {
    [mo setValue:[NSNumber numberWithInt:i++] forKey:@"displayOrder"];
  }

  [things release], things = nil;

  [managedObjectContext save:nil];
}
Run Code Online (Sandbox Code Playgroud)

Apple文档还包含重要提示:

https://developer.apple.com/library/ios/#documentation/CoreData/Reference/NSFetchedResultsControllerDelegate_Protocol/Reference/Reference.html

如何实现CoreData记录的重新排序中也提到了这一点

引用Apple文档:

用户驱动的更新

通常,NSFetchedResultsController旨在响应模型层的更改.如果您允许用户重新排序表行,那么您的委托方法的实现必须考虑到这一点.

通常,如果允许用户重新排序表行,则模型对象具有指定其索引的属性.当用户移动行时,您会相应地更新此属性.但是,这会导致控制器注意到更改的副作用,因此通知其委托更新(使用controller:didChangeObject:atIndexPath:forChangeType:newIndexPath :).如果您只是使用"典型使用"中显示的此方法的实现,则委托会尝试更新表视图.但是,由于用户的操作,表视图已处于适当的状态.

因此,通常,如果您支持用户驱动的更新,则应在用户启动移动时设置标志.在委托方法的实现中,如果设置了标志,则绕过主方法实现; 例如:

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

    if (!changeIsUserDriven) {
        UITableView *tableView = self.tableView;
        // Implementation continues...
Run Code Online (Sandbox Code Playgroud)