2009-06-29 53 views
37

更新刪除部分的UITableView:動畫

我已經發布我解決這個問題,如下回答。它採用了與我的第一次修訂不同的方法。


原始的問題 我以前問的SO一個問題,我認爲解決我的問題:

How to deal with non-visible rows during row deletion. (UITableViews)

不過,我現在再次從一個UITableView刪除部分時,也有類似的問題。 (當我改變表中的部分/行數時,它們重新出現了)。

在我因爲我的文章的剪切長度而失去你之前,讓我清楚地說明問題,並且你可以根據需要閱讀以提供答案。


問題:

如果批量刪除從一個UITableView,應用程序崩潰的行和段,有時。這取決於表格的配置以及我選擇刪除的行和部分的組合。

日誌說我撞因爲它說我還沒有更新數據源,並適當處理表:

Invalid update: invalid number of rows in section 5. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted). 

現在很快,你寫明顯的答案之前,我向你保證我的確添加並刪除了行和部分正確地來自dataSource。解釋是漫長的,但你會在下面找到它,遵循這個方法。

所以用的是,如果你仍然有興趣...


方法處理去除部分和行:

- (void)createFilteredTableGroups{ 

    //index set to hold sections to remove for deletion animation 
    NSMutableIndexSet *sectionsToDelete = [NSMutableIndexSet indexSet]; 
    [sectionsToDelete removeIndex:0]; 


    //array to track cells for deletion animation 
    NSMutableArray *cellsToDelete = [NSMutableArray array]; 

    //array to track controllers to delete from presentation model 
    NSMutableArray *controllersToDelete = [NSMutableArray array]; 

    //for each section 
    for(NSUInteger i=0; i<[tableGroups count];i++){ 

     NSMutableArray *section = [tableGroups objectAtIndex:i]; 

     //controllers to remove 
     NSMutableIndexSet *controllersToDeleteInCurrentSection = [NSMutableIndexSet indexSet]; 
     [controllersToDeleteInCurrentSection removeIndex:0]; 
     NSUInteger indexOfController = 0; 

     //for each cell controller 
     for(ScheduleCellController *cellController in section){ 

      //bool indicating whether the cell controller's cell should be removed 
      NSString *shouldDisplayString = (NSString*)[[cellController model] objectForKey:@"filteredDataSet"]; 
      BOOL shouldDisplay = [shouldDisplayString boolValue]; 

      //if it should be removed 
      if(!shouldDisplay){ 

       NSIndexPath *cellPath = [self indexPathOfCellWithCellController:cellController]; 

       //if cell is on screen, mark for animated deletion 
       if(cellPath!=nil) 
        [cellsToDelete addObject:cellPath]; 

       //marking controller for deleting from presentation model 
       [controllersToDeleteInCurrentSection addIndex:indexOfController];     

      } 
      indexOfController++; 
     } 

     //if removing all items in section, add section to removed in animation 
     if([controllersToDeleteInCurrentSection count]==[section count]) 
      [sectionsToDelete addIndex:i]; 

     [controllersToDelete addObject:controllersToDeleteInCurrentSection]; 

    } 


    //copy the unfiltered data so we can remove the data that we want to filter out 
    NSMutableArray *newHeaders = [tableHeaders mutableCopy]; 
    NSMutableArray *newTableGroups = [[allTableGroups mutableCopy] autorelease]; 


    //removing controllers 
    int i = 0; 
    for(NSMutableArray *section in newTableGroups){ 
     NSIndexSet *indexesToDelete = [controllersToDelete objectAtIndex:i]; 
     [section removeObjectsAtIndexes:indexesToDelete]; 
     i++; 
    } 

    //removing empty sections and cooresponding headers 
    [newHeaders removeObjectsAtIndexes:sectionsToDelete]; 
    [newTableGroups removeObjectsAtIndexes:sectionsToDelete]; 

    //update headers 
    [tableHeaders release]; 
    tableHeaders = newHeaders; 

    //storing filtered table groups 
    self.filteredTableGroups = newTableGroups; 


    //filtering animation and presentation model update 
    [self.tableView beginUpdates]; 
    tableGroups = self.filteredTableGroups; 
    [self.tableView deleteSections:sectionsToDelete withRowAnimation:UITableViewRowAnimationTop]; 
    [self.tableView deleteRowsAtIndexPaths:cellsToDelete withRowAnimation:UITableViewRowAnimationTop]; 
    [self.tableView endUpdates]; 


    //marking table as filtered 
    self.tableIsFiltered = YES; 


} 

我的猜測:

問題他們似乎是這樣的:如果你看上面列出了每個部分的單元格數量,你會看到第5部分看起來增加了1.但是,這是不正確的。原來的第5部分實際上已被刪除,而另一部分取而代之(具體而言,它是舊的第10部分)。

那麼爲什麼表格視圖似乎沒有意識到這一點?它應該知道我刪除了舊部分不應該期望現在位於舊部分索引處的新部分受到刪除部分行數的限制。

希望這是有道理的,這寫出來有點複雜。

(請注意,此代碼之前使用不同數量的行/部分工作。這種特定的配置似乎給它的問題)

回答

87

我以前碰到這個問題。您正嘗試刪除某個部分的所有行,然後再刪除該部分。但是,僅刪除該部分就足夠(並且適當)。其中的所有行也將被刪除。以下是我的項目中處理刪除一行的示例代碼。它需要確定它是否應該從節中刪除僅此行或刪除整個部分,如果它是最後剩下的行中的那一節:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    if (editingStyle == UITableViewCellEditingStyleDelete) 
    { 
     // modelForSection is a custom model object that holds items for this section. 
     [modelForSection removeItem:[self itemForRowAtIndexPath:indexPath]]; 

     [tableView beginUpdates]; 

     // Either delete some rows within a section (leaving at least one) or the entire section. 
     if ([modelForSection.items count] > 0) 
     { 
      // Section is not yet empty, so delete only the current row. 
      [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] 
          withRowAnimation:UITableViewRowAnimationFade]; 
     } 
     else 
     { 
      // Section is now completely empty, so delete the entire section. 
      [tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section] 
        withRowAnimation:UITableViewRowAnimationFade]; 
     } 

     [tableView endUpdates]; 
    } 
} 
4

我注意到,你從表中第一個刪除部分,然後刪除行。

我知道有該表視圖編程指南UITableViews一個complicated discussion of batch insertion and deletion,但它並沒有具體涉及這一點。

我認爲發生的事情是,刪除的部分是造成行刪除指錯了行。

即要刪除節#2和#段4列#1 ... ...但你已經刪除後段#2,老款#4是第三部分,讓你當你刪除(4,1)的舊NSIndexPath將刪除一些可能不存在的隨機不同行。

所以我覺得修復可能是作爲交換的代碼這兩行一樣簡單,所以你先刪除行,然後切片。

+0

或者,跟蹤您需要擺脫的每個單元格的indexPath,並在您執行刪除操作時適當調整它們。 (這可能是漫長的/令人費解的/不恰當的方式 - 只是一個念頭。) – Tim 2009-07-02 04:07:48

3

所以最後,這裏是我解決這個問題。 這種方法可以適用於任何大小的表格,任何數量的段(據我所知)

像以前一樣,我修改了Matt Gallagher的tableview Code,它將單元特定的邏輯放置在單獨的單元控制器中。但是,你可以在這個方法很容易適應不同的模型

我加入以下(相關)的ivars馬特代碼:

NSArray *allTableGroups; //always has a copy of every cell controller, even if filtered 
NSArray *filteredTableGroups; //always has a copy of the filtered table groups 

馬特的原伊娃:

NSArray *allTableGroups 

...總是點到上面的數組之一。

這或許可以被重構和顯著改善,但我還沒有需要。另外,如果你使用核心數據,NSFetchedResultsController使這更容易。

現在到方法(我想發表評論,就像我可以):

- (void)createFilteredTableGroups{ 

    //Checking for the usual suspects. all which may through an exception 
    if(model==nil) 
     return; 
    if(tableGroups==nil) 
     return; 
    if([tableGroups count]==0) 
     return; 


    //lets make a new array to work with 
    NSMutableArray *newTableGroups = [[allTableGroups mutableCopy] autorelease]; 

    //telling the table what we are about to do 
    [self.tableView beginUpdates]; 


    //array to track cells for deletion animation 
    NSMutableArray *indexesToRemove = [NSMutableArray array]; 

    //loop through each section 
    for(NSMutableArray *eachSection in tableGroups){ 

     //keeping track of the indexes to delete for each section 
     NSMutableIndexSet *indexesForSection = [NSMutableIndexSet indexSet]; 
     [indexesForSection removeAllIndexes]; 

     //increment though cell indexes 
     int rowIndex = 0; 

     //loop through each cellController in the section 
     for(ScheduleCellController *eachCellController in eachSection){ 

      //Ah ha! A little magic. the cell controller must know if it should be displayed. 
      //This you must calculate in your business logic 
      if(![eachCellController shouldDisplay]){ 

       //add non-displayed cell indexes 
       [indexesForSection addIndex:rowIndex]; 

      } 
      rowIndex++; 
     } 
     //adding each array of section indexes, EVEN if it is empty (no indexes to delete) 
     [indexesToRemove addObject:indexesForSection]; 

    } 

    //Now we remove cell controllers in newTableGroups and cells from the table 
    //Also, each subarray of newTableGroups is mutable as well 
    if([indexesToRemove count]>0){ 

     int sectionIndex = 0; 
     for(NSMutableIndexSet *eachSectionIndexes in indexesToRemove){ 

      //Now you know why we stuck the indexes into individual arrays, easy array method 
      [[newTableGroups objectAtIndex:sectionIndex] removeObjectsAtIndexes:eachSectionIndexes]; 

      //tracking which cell indexPaths to remove for each section 
      NSMutableArray *indexPathsToRemove = [NSMutableArray array]; 
      int numberOfIndexes = [eachSectionIndexes count]; 

      //create array of indexPaths to remove 
      NSUInteger index = [eachSectionIndexes firstIndex]; 
      for(int i = 0; i< numberOfIndexes; i++){ 

       NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:sectionIndex]; 
       [indexPathsToRemove addObject:indexPath]; 
       index = [eachSectionIndexes indexGreaterThanIndex:index]; 
      } 

      //delete the rows for this section 
      [self.tableView deleteRowsAtIndexPaths:indexPathsToRemove withRowAnimation:UITableViewRowAnimationTop]; 

      //next section please 
      sectionIndex++; 
     } 

    } 

    //now we figure out if we need to remove any sections 
    NSMutableIndexSet *sectionsToRemove = [NSMutableIndexSet indexSet]; 
    [sectionsToRemove removeAllIndexes]; 

    int sectionsIndex = 0; 
    for(NSArray *eachSection in newTableGroups){ 

     //checking for empty sections 
     if([eachSection count]==0) 
      [sectionsToRemove addIndex:sectionsIndex]; 

     sectionsIndex++; 
    } 

    //updating the table groups 
    [newTableGroups removeObjectsAtIndexes:sectionsToRemove]; 

    //removing the empty sections 
    [self.tableView deleteSections:sectionsToRemove withRowAnimation:UITableViewRowAnimationTop]; 

    //updating filteredTableGroups to the newTableGroups we just created 
    self.filteredTableGroups = newTableGroups; 

    //pointing tableGroups at the filteredGroups 
    tableGroups = filteredTableGroups; 

    //invokes the animation 
    [self.tableView endUpdates]; 


} 
1

我看到這個完全相同的錯誤作爲的過早釋放我的自定義的tableview單元格的背景查看結果。

隨着NSZombieEnabled我有一個例外是一路下跌的內部調用下面拋出一個函數來準備電池再利用。沒有NSZombieEnabled,我得到了內部一致性錯誤。

順便說一句,當我固定在單元格的背景視圖中的保留/釋放的問題,我能夠刪除段的最後一排,而無需顯式刪除該部分。

故事的道德:這個錯誤僅僅意味着當你嘗試刪除時發生了一些不好的事情,而當你刪除時發生的事情之一就是這個單元格準備重新使用,所以如果你正在做任何自定義的事情tableview單元格,在那裏尋找可能的錯誤。

0

或只是這樣做

- (void)tableView:(UITableView *)tv  
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle 
forRowAtIndexPath:(NSIndexPath *)indexPath { 

if(editingStyle == UITableViewCellEditingStyleDelete) {  
    //Delete the object from the table. 
    [directoriesOfFolder removeObjectAtIndex:indexPath.row]; 
    [tv deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] 
withRowAnimation:UITableViewRowAnimationFade]; 
} 
} 

文件夾的目錄是你的陣!那是所有上面的代碼不適合我!這樣做更便宜,而且合理!

2

我懷疑你忘記從你的內部存儲中刪除表示該部分的對象,所以-numberOfSectionsInTableView:方法在所有部分被刪除後仍然返回1。

這正是我在做同樣的事情時做錯了!

1

一個更加簡單的解決這種方式來更新您的數據源,然後調用reloadSections

[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade]; 

這將重新加載一個部分。或者,您可以使用indexSetWithIndexesInRange:同時重新加載多個部分。