0

My application have UI similar to Phone.app->Recents: sectioned UITableView and a UISegmentedControl in the navigation bar. What I want to do is display full set of data if first section is selected and display filtered set of data if second section is selected.

When user selects second item in UISegmentedControl I delete specific rows from the table view. Here is the code:

[tableView beginUpdates];
NSMutableArray *indexPaths = [NSMutableArray array];
/// ... fill up indexPaths with row indexes
[tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];

The code above works fine except for one serious issue: performance. Deleting 1500 out of 2200 rows takes about 20 seconds. That is obviously unacceptable. What is the best approach to filtering table view rows with animation?

sgosha
  • 1,299
  • 12
  • 19

3 Answers3

1

For large changes to your data source, it is recommended that you use

[tableView reloadData]

instead of

[tableView beginUpdates];
// changes here ....
[tableView endUpdates];

EDIT: I haven't tried this approach myself, but consider altering only those rows that are contained in the collection of visible cells, perhaps with a buffer above and below. You can get the indexPaths of the visible cells by calling

[tableView indexPathsForVisibleRows];
nduplessis
  • 12,236
  • 2
  • 36
  • 53
  • Does iOS SDK documentation specifies recommended maximum of rows deleted/inserted between `beginUpdates` and `endUpdates` in order to keep animations smooth? – sgosha May 03 '11 at 19:56
  • Not that I'm aware off, but reloadData is only concerned with the visible cells in the table view which makes it much faster. I'm sure you can experiment with the exact amount, but it will most probably be way way below 1500 – nduplessis May 03 '11 at 20:06
0

I definitely agree with @nduplessis that you should reload the dataSource rather than manipulating the view. I proposed a solution to a similar question here that would indeed cause your rows to slide up.

The basic idea is to call reloadSections:withRowAnimation: and in your UITableViewDataSource methods switch on the segmented control's selectedSegmentIndex.

Assuming your data is flat (only one section) it would look something like this:

- (IBAction)segmentSwitch:(id)sender
{
    [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    switch (self.segmentedControl.selectedSegmentIndex)
    {
        default:
        case 0:
            return [self.allRows count];
        case 1:
            return [self.onlySomeRows count];
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    id data;
    switch (self.segmentedControl.selectedSegmentIndex)
    {
        default:
        case 0:
            data = [self.allRows objectAtIndex:[indexPath row]];
            break;
        case 1:
            data = [self.onlySomeRows objectAtIndex:[indexPath row]];
            break;
    }

    //TODO: use data to populate and return a UITableViewCell...
}
Community
  • 1
  • 1
0

How about using a two arrays? One of them is the full data set and the other one is the filtered dataset.

This way you can have two different tableviews, and depending on what the selected segment is, you could do a fade animation between the two tableviews. For example, lets say you select a segment:

-(void)switchTableViews
{
   [UIView beginAnimations:nil context:NULL];
   [UIView setAnimationBeginsFromCurrentState:YES];
   [UIView setAnimationCurve:UIViewAnimationCurveLinear];
   [UIView setAnimationDuration:0.5];
   [UIView setAnimationDidStopSelector:@selector(hideTableView)];

   switch (segmentedControl.selectedSegmentIndex) 
   {
      case 0:
      {
         tableView1.alpha = 1.0;
         tableView2.alpha = 0.0;
      }
      break;
      case 1:
      {
         tableView1.alpha = 0.0;
         tableView2.alpha = 1.0;
      }
      break;
      default:
      break;
   }

   [UIView commitAnimations];
}

- (void)hideTableView
{
   switch (segmentedControl.selectedSegmentIndex) 
   {
      case 0:
      {
         tableView1.hidden = NO;
         tableView2.hidden = YES;
      }
      break;
      case 1:
      {
         tableView1.hidden = YES;
         tableView2.hidden = NO;
      }
      break;
      default:
      break;
   }
}

Of course, this would mean setting up different code sets for the datasource methods but it's not as difficult as you think. Just use a simple if-else to check for which tableview is being set.

Sid
  • 9,508
  • 5
  • 39
  • 60
  • yeah, it won't be hard to do. but solution you suggested won't make rows slide up in place of rows being deleted - it will be just a transition from one view to another. please go to Phone->Recents and try switching between 'All' and 'Missed' to see what kind of animation I want to see in my app. – sgosha May 03 '11 at 19:48
  • Well I can't think of an alternative to what you want that would handle the performance issues sorry :( Do you really need to show 2000 rows at once? Can you not implement paging (a "Load More" on the last cell that adds 100 more rows to the tableview). That would REALLY help the performance not just in deleting rows, but rendering the tableview also. Other than that I don't think I could offer a solution :( – Sid May 03 '11 at 19:57