代码之家  ›  专栏  ›  技术社区  ›  Greg Combs

用核心数据重新排列UITableView[重复]

  •  23
  • Greg Combs  · 技术社区  · 15 年前

    可能重复:
    How to implement re-ordering of CoreData records?

    我试图找到一个代码示例,演示如何在表视图中处理移动/重新排列单元格,当单元格使用FetchedResultsController(即与核心数据结合使用)时。我正在获取moverowatindexpath:调用我的数据源,但我找不到正确的黑色魔术组合来获取表/数据以正确识别更改。

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

    世界跆拳道联盟。我需要以某种方式使提取结果控制器失效吗?每当我把它设为零,我就会崩溃。我应该释放它吗?我在杂草丛中吗?

    这是我现在在里面的东西…

    - (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];
        }
    }
    
    8 回复  |  直到 12 年前
        1
  •  8
  •   Louis Gerbarg    15 年前

    通常,当您看到这样的工件时,正在发生的事情是,用户界面已经动画到一个新的位置并告诉了您关于它的信息,那么您对模型所做的更新并不能正确地反映出在视图下次必须引用模型进行更新时会导致故障的状态。

    我认为你不完全理解你在这个方法中应该做什么。之所以调用它是因为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];
        }
    

    关键是,您实际上并没有更改基础模型中的顺序。这些索引路径来自uitableviewcontroller,它告诉您用户在这些路径之间拖动到点,您需要根据更新基础数据。但是,FetchedResultsController始终是按排序顺序排列的,因此,在更改这些属性之前,不会移动任何内容。

    问题是,它们没有被移动,您被调用来告诉您需要移动它们(通过调整Sortable属性)。你真正需要的是:

    NSNumber *targetOrder = [fetchedResultsController objectAtIndexPath:toIndexPath];
    LinkObj *link = [fetchedResultsController objectAtIndexPath:FromPath];
    link.order = targetOrder;
    

    这将导致对象重新排序,然后遍历并清除其他应该向上移动的对象的任何顺序号,同时注意索引可能已移动。

        2
  •  7
  •   Greg Combs    15 年前

    下面是现在正式工作的内容,包括删除、移动和插入。每当有编辑操作影响订单时,我都会“验证”订单。

    - (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);
        }
    }
    
        3
  •  5
  •   Tom    12 年前

    在表视图中移动行时, 实际上,您将一块其他行(至少由一行组成)移动到另一个方向。 同时。技巧是只更新此块和已移动项的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;
              }
           }
        }
    }
    

    然后,您只需更新移动项的位置以及fromindexpath和toindexxpath之间的所有项的位置:

    - (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);
        }
    }
    
        4
  •  5
  •   Community dbr    7 年前

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

    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];
    }
    

    苹果文档还包含重要提示:

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

    这也在 How to implement re-ordering of CoreData records?

    引用苹果文档:

    用户驱动的更新

    通常,nsfetchedresultsController是为响应 模型层的更改。如果允许用户重新排序表行, 那么,委托方法的实现必须考虑到 帐户。

    通常,如果您允许用户重新排序表行,您的模型 对象具有指定其索引的属性。当用户移动时 一行,您将相应地更新此属性。然而,这有 导致控制器注意到更改的副作用,因此 通知其代表更新(使用 控制器: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...
    
        5
  •  1
  •   Gregory Ray    15 年前
    - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{
        [self.pairs exchangeObjectAtIndex:sourceIndexPath.row withObjectAtIndex:destinationIndexPath.row];
        [self performSelector:@selector(reloadData) withObject:nil afterDelay:0.02];
    }
    
    - (void)reloadData{
        [table reloadData];
    }
    

    工作台移动时无法重新加载,请稍后重新加载,这样就可以了。

        6
  •  1
  •   Jorge Ortiz    15 年前

    对不起,格雷格,我肯定我做错了什么,但你的回答对我不起作用。

    尽管我的所有对象都正确验证,但当我退出编辑模式时,其中一行冻结(编辑控件不会消失),单元随后不会正确响应。

    也许我的问题是我不知道如何使用你设置的移动属性(self.moving=yes)。你能澄清一下吗?非常感谢你。

    乔治

        7
  •  1
  •   Community dbr    7 年前

    这是实现此类功能的非常困难的方法。 这里可以找到更简单和优雅的方式: UITableView Core Data reordering

        8
  •  1
  •   Sangappa Paraddi    14 年前

    可以使用[<gt;ISediting]确定是否启用了表的编辑。而不是按照下面的语句建议延迟

    [表performselector:@selector(reloaddata)withobject:nil afterdelay:0.02];