Thursday, June 9, 2016

UITableView dequeueReusableCellWithIdentifier

In the UITableView data source delegate method 'cellForRowAtIndexPath', We are supposed to return a UITableViewCell instance. We should be careful here with respect to the performance of the UITableView.

If our table view is going to be static, We can directly allocated and initialize UITableViewCell and return that cell in the delegate method. As the table view is not scrollable (having less data), all the cell intializations will happen only one time.


                         




Approach #1





  - (UITableViewCell *)tableView:(UITableView *)tableView 
                cellForRowAtIndexPath:(NSIndexPath *)indexPath 
                                                                                  
 {  
    
      static NSString *CellIdentifier = @"Categories";
    
      UITableViewCell *cell = [[UITableViewCell alloc
                                                 initWithStyle:UITableViewCellStyleDefault 
                                                                 reuseIdentifier:@"Categories"];

      [cell setBackgroundColor:[UIColor clearColor]];        
        
      cell.textLabel.text = str;
      [cell.textLabel setTextColor:[UIColor whiteColor]];
    
    
      return cell;

 }



To know more about static variables, You can have a look at,


What If there are hundreds or thousands of cells needs to be created when there is huge data we need to display. Every time when user scrolls each and everytime gets intialized every time. This is definitely a hit on tableview's performance and also based on user scrolling lodas of memory may get allocated for the cells. If our cell is having so many custom subviews in it, Then  the situation will get more worse.

With our Approach #1, If we have some 200 records, We are initializing all the cells though the cells are having same format. Definitely, this approach doesn't work for huge data.

To avoid this perform hit and memory usage, We can use tableview's dequeueReusableCellWithIdentifier, Which reuses already created cell.

Let's change our Approach #1 and use the dequeueReusableIdentifier and see what happens.


Approach #2 (dequeueReusableCellWithIdentifier)





  - (UITableViewCell *)tableView:(UITableView *)tableView 
                                      cellForRowAtIndexPath:(NSIndexPath *)indexPath
                                                                                        
  {  
    
      static NSString *CellIdentifier = @"Categories";
    
      UITableViewCell *cell = [tableView 
                                            dequeueReusableCellWithIdentifier:CellIdentifier];
    
      NSString *str = [NSString stringWithFormat:@"cell - %ld",(long)indexPath.row];
    
      if(cell == nil){        
        
          cell = [[UITableViewCell allocinitWithStyle:UITableViewCellStyleDefault 
                                                                       reuseIdentifier:@"Categories"];

          [cell setBackgroundColor:[UIColor clearColor]];
        
      }    
    
      cell.textLabel.text = str;
      [cell.textLabel setTextColor:[UIColor whiteColor]];    
    
      return cell;


  }



This Approach #2 checks first whether there is any reusable cell is available and uses that cell for creating any new cell. If there is no cell available for reuse, We can go for a new cell initialization.

Place a breakpoint in the cell == nil condition, to check how many times the cell initializations is happening.

Here, You may get a question like, With this approach, 

1. How many cells will get initialized?
2. How this approach mitigates/avoids performance hit and memory usage?


                         



Let me give answers to those questions one by one.


1. How many cells will get initialized?


Let's give a height of 200px to our tableview and cell height as 40px.




  [self.myTableView setFrame:CGRectMake(25100300200)];

  - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:
           (NSIndexPath *)indexPath{
    
      return 40;


  }



So we will be having (200%40 = ) 5 visible cells. 5 cells will be initialized first.


cell - 0
cell - 1
cell - 2
cell - 3
cell - 4


When user starts scrolling, The 6th cell (cell - 5) will get initialized. That's it. After that, There won't be any cell initializations. The table starts reusing these already initialized cells. Though you are having 1000 cells after that, There won't be any cell initializations.

So, Only 6 cells will go inside cell == nil condition.




    if(cell == nil){        
        
        cell = [[UITableViewCell allocinitWithStyle:
                          UITableViewCellStyleDefault reuseIdentifier:@"Categories"];
                                                                                
        [cell setBackgroundColor:[UIColor clearColor]];
        

    }    



Technically, though we have 1000 cells only visible cells +1 will only get initialized.

In our case, only 6 (5 visible +1) cells will get initialized.


2. How this approach mitigates/avoids performance hit and memory usage?


With this approach, Instead of creating 1000 cells, We are reusing the cells created. That's how the performance of the tableview will also get improved a lot as we are not creating new cells every time the user scrolls. The table view becomes very smooth while scrolling and gives a very good user experience.

Hope this post is useful. Feel free to comment incase of any queries.