2009-06-29 31 views
37

Cập nhậtUITableView: xóa phần với hình ảnh động

Tôi đã đăng giải pháp của tôi cho vấn đề này như một câu trả lời dưới đây. Nó có một cách tiếp cận khác với bản sửa đổi đầu tiên của tôi.


Câu hỏi gốc trước đây tôi hỏi một câu hỏi về SO mà tôi nghĩ giải quyết vấn đề của tôi:

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

Tuy nhiên, tôi bây giờ có vấn đề tương tự một lần nữa khi loại bỏ phần từ một UITableView. (chúng nổi lên khi tôi thay đổi số lượng phần/hàng trong bảng).

Trước khi tôi mất bạn vì độ dài cắt của bài đăng của tôi, hãy để tôi nêu rõ vấn đề và bạn có thể đọc nhiều như bạn yêu cầu để cung cấp câu trả lời.


Vấn đề:

Nếu hàng loạt xóa dòng VÀ phần từ một UITableView, các ứng dụng bị treo, đôi khi. Nó phụ thuộc vào cấu hình của bảng và sự kết hợp của các hàng và phần mà tôi chọn để loại bỏ.

Nhật ký nói rằng tôi bị rơi vì nó nói tôi đã không cập nhật các nguồn dữ liệu và bảng đúng:

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). 

Bây giờ một cách nhanh chóng, trước khi bạn viết câu trả lời rõ ràng, tôi đảm bảo với bạn tôi đã thực sự bổ sung và xóa các hàng và phần chính xác từ dataSource. Giải thích là dài, nhưng bạn sẽ tìm thấy nó dưới đây, theo phương pháp.

Vì vậy, với điều đó, nếu bạn vẫn quan tâm ...


Phương pháp xử lý loại bỏ các bộ phận và các hàng:

- (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; 


} 

tôi đoán:

Các probl em có vẻ như thế này: Nếu bạn nhìn ở trên nơi tôi liệt kê số lượng ô trong mỗi phần, bạn sẽ thấy phần 5 xuất hiện để tăng thêm 1. Tuy nhiên, điều này không đúng. Phần 5 ban đầu đã thực sự bị xóa và một phần khác đã thay thế vị trí của nó (cụ thể, đó là phần 10 cũ).

Vậy tại sao chế độ xem bảng dường như không nhận ra điều này? Nên BIẾT rằng tôi đã xóa phần cũ không nên mong đợi một phần mới hiện đang nằm ở chỉ mục của mục cũ bị ràng buộc bởi số hàng của phần đã xóa.

Hy vọng điều này có ý nghĩa, có một chút phức tạp khi viết điều này.

(lưu ý mã này đã hoạt động trước với số hàng/phần khác nhau.cấu hình cụ thể này dường như cung cấp cho nó các vấn đề)

Trả lời

87

Tôi đã gặp sự cố này trước đây. Bạn đang cố xóa tất cả các hàng khỏi một phần và sau đó, ngoài ra, phần trống bây giờ đó. Tuy nhiên, nó là đủ (và thích hợp) để loại bỏ phần đó chỉ. Tất cả các hàng trong đó cũng sẽ bị xóa. Đây là một số mã mẫu từ dự án của tôi xử lý việc xóa một hàng. Nó cần phải xác định xem nó nên loại bỏ chỉ hàng này từ một phần hoặc xóa toàn bộ phần nếu nó là hàng cuối cùng còn lại trong phần đó:

- (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

Tôi nhận thấy rằng bạn đang xóa các phần khỏi bảng trước, sau đó xóa các hàng.

Tôi biết có một complicated discussion of batch insertion and deletion cho UITableViews trong Hướng dẫn lập trình xem bảng, nhưng nó không cụ thể bao gồm điều này.

Tôi nghĩ điều đang xảy ra là việc xóa các phần đang khiến hàng bị xóa để tham chiếu đến hàng sai.

nghĩa là bạn muốn xóa phần # 2 và hàng # 1 từ phần # 4 ... nhưng sau khi bạn đã xóa phần # 2, phần cũ # 4 bây giờ là phần thứ ba, vì vậy bạn khi bạn xóa bằng NSIndexPath cũ của (4, 1) bạn đang xóa một số hàng ngẫu nhiên khác nhau có thể không tồn tại.

Vì vậy, tôi nghĩ bản sửa lỗi có thể đơn giản như hoán đổi hai dòng mã đó, vì vậy bạn đang xóa các hàng trước tiên, sau đó là các phần.

+0

Cách khác, theo dõi chỉ mụcPath cho mỗi ô bạn cần loại bỏ và điều chỉnh chúng một cách thích hợp khi bạn thực hiện xóa. (Đây có thể là cách dài/phức tạp/không thích hợp để làm điều đó - chỉ là một ý nghĩ.) – Tim

+0

Tôi đang xóa hàng loạt, vì vậy nó không có sự khác biệt theo thứ tự mà tôi liệt kê các hoạt động. Chế độ xem bảng thực hiện các thao tác "cùng một lúc" khi chúng nằm trong khối cập nhật. Như tôi đã hoang tưởng, tôi đã cố gắng chuyển đổi thứ tự của các hoạt động vô ích. Việc đánh số các phần/hàng không chuyển đổi (không nên chuyển đổi) trong quá trình xóa hàng loạt. Nếu không sử dụng các khối, bạn sẽ chính xác. –

+0

@Tim Thú vị nghĩ. Bạn nói đúng, có thể khá tẻ nhạt với một số lượng lớn các xóa (mà tôi sẽ có). Tôi cũng tự hỏi liệu tôi có thể thực hiện nhiều lần xóa liên tiếp nhanh chóng hay không. Tôi đã cố gắng xóa hàng loạt để tránh những vấn đề này, nhưng nó có thể là cần thiết. –

3

Vì vậy, cuối cùng là giải pháp của tôi cho vấn đề này. Phương pháp này có thể được áp dụng cho các bảng có kích thước bất kỳ, bất kỳ số phần nào (theo như tôi có thể biết)

Như trước đây tôi đã sửa đổi bảng phân tích của Matt Gallagher. Tuy nhiên, bạn có thể dễ dàng thích nghi với phương pháp này để một mô hình khác nhau

Tôi đã thêm ivars sau (có liên quan) vào mã của Matt:

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

Ivar gốc của Matt:

NSArray *allTableGroups 

... luôn điểm đến một trong các mảng trên.

Điều này có thể được tái cấu trúc và cải thiện đáng kể, nhưng tôi không có nhu cầu. Ngoài ra, nếu bạn sử dụng Core Data, NSFetchedResultsController sẽ làm cho việc này trở nên dễ dàng hơn.

Bây giờ trên các phương pháp (Tôi cố gắng để bình luận nhiều như tôi có thể):

- (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

tôi thấy lỗi này chính xác giống như kết quả của việc sớm phát hành quan điểm nền của tế bào tùy chỉnh tableview tôi.

Với NSZombieEnabled, tôi nhận được một ngoại lệ được ném xuống dưới một cuộc gọi nội bộ đến một hàm để chuẩn bị ô để sử dụng lại. Nếu không có NSZombieEnabled, tôi đã nhận được lỗi nhất quán Nội bộ.

Ngẫu nhiên khi tôi khắc phục sự cố lưu giữ/giải phóng ở chế độ xem nền của ô, tôi có thể xóa hàng cuối cùng của phần mà không phải xóa phần đó một cách rõ ràng.

Đạo đức của câu chuyện: Lỗi này chỉ có nghĩa là điều gì đó đang xảy ra khi bạn cố xóa và một trong những điều xảy ra khi bạn xóa là ô được chuẩn bị để sử dụng lại, vì vậy nếu bạn đang làm bất kỳ điều gì tùy chỉnh ô xem bảng, tìm lỗi có thể xảy ra ở đó.

0

hoặc chỉ làm điều này

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

thư mục của thư mục là mảng của bạn! Thats là tất cả các mã trên không làm việc cho tôi! Điều này là ít tốn kém để làm và chỉ có ý nghĩa!

2

Tôi nghi ngờ rằng bạn đang quên xóa đối tượng đại diện cho phần khỏi bộ nhớ trong của bạn, để phương thức -numberOfSectionsInTableView: vẫn trả về 1 sau khi tất cả các phần bị xóa.

Đó chính xác là những gì tôi đã làm sai khi tôi gặp sự cố tương tự!

1

Một cách đơn giản hơn nhiều để giải quyết này là để cập nhật nguồn dữ liệu của bạn, sau đó gọi reloadSections

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

này sẽ tải lại một phần duy nhất. Hoặc bạn có thể sử dụng indexSetWithIndexesInRange: để tải lại nhiều phần cùng một lúc.

Các vấn đề liên quan