How Does Cellforrowatindexpath Work

How does cellForRowAtIndexPath work?

1) The function returns a cell for a table view yes? So, the returned object is of type UITableViewCell. These are the objects that you see in the table's rows. This function basically returns a cell, for a table view.
But you might ask, how the function would know what cell to return for what row, which is answered in the 2nd question

2)NSIndexPath is essentially two things-

  • Your Section
  • Your row

Because your table might be divided to many sections and each with its own rows, this NSIndexPath will help you identify precisely which section and which row. They are both integers. If you're a beginner, I would say try with just one section.

It is called if you implement the UITableViewDataSource protocol in your view controller. A simpler way would be to add a UITableViewController class. I strongly recommend this because it Apple has some code written for you to easily implement the functions that can describe a table. Anyway, if you choose to implement this protocol yourself, you need to create a UITableViewCell object and return it for whatever row. Have a look at its class reference to understand re-usablity because the cells that are displayed in the table view are reused again and again(this is a very efficient design btw).

As for when you have two table views, look at the method. The table view is passed to it, so you should not have a problem with respect to that.

how cellforrowatindexpath works in objective c


-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

This is delegate method of UITableView. The returned object is of type UITableViewCell. These are the objects that you see in the table's rows. NSIndexPath has two this Section and Row.

It is called if you implement the UITableViewDataSource protocol in your view controller. A simpler way would be to add a UITableViewController class. I strongly recommend this because it Apple has some code written for you to easily implement the functions that can describe a table. Anyway, if you choose to implement this protocol yourself, you need to create a UITableViewCell object and return it for whatever row. Have a look at its class reference to understand re-usablity because the cells that are displayed in the table view are reused again and again(this is a very efficient design btw).

If your implementing custom cell then I will strongly recommend you use

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 

to return only empty cell not set here. use thing like

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{

CartTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"MyCart_Cell" forIndexPath:indexPath];
return cell;
}

and the use this delegate which will called just after cellForRow data source method

- (void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{


if ([cell isKindOfClass:[CartTableViewCell class]])
{
CartTableViewCell *cell1 = (CartTableViewCell*)cell;
MyCartModel* data = [_myCartProductArray objectAtIndex:indexPath.row];
[cell1 setUpData:data];
}

}

and set data on UILabel in UITableviewcell custom class.

tableView: cellForRowAtIndexPath: get called not only for visible cells?

Well, I somehow dealt with my problem. Here are my ideas and thoughts how I came to the solution. Maybe it could be helpful to somebody.

I've instructed memory allocations and call stack using Instruments during opening section events. It showed me, that the majority of time is spent on loading cell from nib file.

Firstly, that I've done was reducing the size of nib file, i.e. minimizing the number of views used in custom tableview cell (now its only 2 views and 2 labels, instead of 6 views, 2 images and 2 labels before). It gave me some improve in cells loading. Apple documentation suggests to use as few as possible views and do not use transparency. So be attentive to these suggestions.

Secondly, as I discovered earlier, that not all cell are visible which are created by -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *), I decided to reduce somehow the number of loadings new cells from nib file. To achieve this, I've came to simple idea: return blank default cells for invisible rows, while load custom cells from nib for visible ones. Here is the piece of code:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self index:indexPath isInvisibleInTableView:tableView])
return [self getBlankCellForTableView:tableView];

// the rest of the method is the same
...
}

-(BOOL)index:(NSIndexPath*)indexPath isInvisibleInTableView:(UITableView*)tableView
{
NSMutableArray *visibleIndexPaths = [self getExtendedVisibleIndexPathsForTableView:tableView];

return ![visibleIndexPaths containsObject:indexPath];
}

-(UITableViewCell*)getBlankCellForTableView:(UITableView*)tableView
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"IVBlankCell"];
if (!cell)
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"IVBlankCell"] autorelease];

return cell;
}

As you can see, I'm not using just -(NSArray*)indexPathsForVisibleRows method of tableview for detecting visible cells. Instead, I've wrote my own method -(NSMutableArray*)getExtendedVisibleIndexPathsForTableView:(UITableView*)tableView. It was necessary because for some reason, when using -(NSArray*)indexPathsForVisibleRows the cells that are next to the last one visible cell or the cells that are previous to the first one visible cell were created as blank cells and looked like empty cells while scrolling. To overcome this, in -(NSMutableArray*)getExtendedVisibleIndexPathsForTableView: (UITableView*)tableView i'm adding border cells to the visible array cells:

-(NSMutableArray*)getExtendedVisibleIndexPathsForTableView:(UITableView*)tableView{
NSArray *visibleIPs = [tableView indexPathsForVisibleRows];

if (!visibleIPs || ![visibleIPs count])
return [NSMutableArray array];

NSIndexPath *firstVisibleIP = [visibleIPs objectAtIndex:0];
NSIndexPath *lastVisibleIP = [visibleIPs objectAtIndex:[visibleIPs count]-1];

NSIndexPath *prevIndex = ([firstVisibleIP row])?[NSIndexPath indexPathForRow:[firstVisibleIP row]-1 inSection:[firstVisibleIP section]]:nil;
NSIndexPath *nextIndex = [NSIndexPath indexPathForRow:[lastVisibleIP row]+1 inSection:[lastVisibleIP section]];

NSMutableArray *exVisibleIndexPaths = [NSMutableArray arrayWithArray:[tableView indexPathsForVisibleRows]];

if (prevIndex)
[exVisibleIndexPaths addObject:prevIndex];
[exVisibleIndexPaths addObject:nextIndex];

return exVisibleIndexPaths;
}

Thereby, I've reduced the time of opening sections with large number of custom cells, which was proved by Instruments tracing and felt while experiencing the app.

iOS UITableView: what's the different between cellForRowAtIndexPath and willDisplayCell: forRowAtIndexPath:

You are right, cell configuration can (in theory) be done in both methods.

However, almost all UITableView have a data source which implements cellForRowAtIndexPath: (it is a required method in the protocol). On the other hand, the willDisplayCell:forRowAtIndexPath: (which is a method of the delegate, not the data source) is optional.

As configuring a cell is usually dependent on the data you want to show, cellForRowAtIndexPath: is by far the most common place to do cell configuration. (I can't even remember using willDisplayCell:forRowAtIndexPath:).

There's one notable exception: when you are using a storyboard and static cells (instead of cell prototypes), you can't do anything useful in cellForRowAtIndexPath: (because dequeueReusableCellWithIdentifier: returns nil), so you have to do configuration in willDisplayCell:forRowAtIndexPath:, viewWillAppear: or other methods.

@NSDeveloper: you're right. Thanks for the hint.

tableView.dequeueReusableCellWithIdentifier VS tableView.cellForRowAtIndexPath

The tableview tries not to have instances of cell objects for all the indexpaths at all times in memory:

  • 'dequeueReusableCellWithIdentifer' recycles a cell or creates it and should be called in 'cellForRow...'. This always returns a cell instance, either a new one or one that was no longer visible previously. You prepare the cell for display after that.

  • 'cellForRowAtIndexPath(indexPath: NSIndexPath) -> UITableViewCell? // returns nil if cell is not visible or index path is out of range' this doesn't create cells, only gives you access to them. I think it should be avoided as much as possible to prevent accidental leaks and other interference with the tableView.

About your 'setSelected' problem:

  • it's good ideea to implement prepare for reuse (set all values to defaults) in the cell class.
  • you shouldn't need to load cell content again in 'didSelectRow' (the user just tapped on it at that point, so it's loaded)
  • you may need to change between orange/clear colors in the cell's 'setSelected'

when does -tableView:cellForRowAtIndexPath: get called?

-tableView:cellForRowAtIndexPath: gets called whenever new cell is needed, whether when first filling the screen with cells, or when a new cell is scrolled into the screen, but it will never get called if you haven't returned a proper number of sections and rows for your table view, as they will be 0 by default.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [yourArray count];
}

Loading data to use in UITableView cellForRowAtIndexPath

Please see example of lazy-loading table below.

This example provides a table view with infinite number of cells; data for each cell is loaded in a background thread only when all of the following conditions are true:

  • UITableView requests that cell;
  • Cell that data is requested for is visible;
  • UITableView is not scrolling OR scrolling is not decelerated (i.e. scrolling is performed while user’s finger touches the screen).

That is, excessive load of the system during fast scrolling is eliminated, and data is loaded only for cells that user really needs to see.

In this example, time-consuming and thread-blocking data loading operation is emulated for each cell with sleeping a background thread for 0.2 seconds. To use this example in your real application please do the following:

  • Replace implementations of the tableElementPlaceholder getter;
  • In the performActualFetchTableCellDataForIndexPaths: method, replace the following line with your actual loading cell data:

    [NSThread sleepForTimeInterval:0.2]; // emulation of time-consuming and thread-blocking operation

  • Tune implementation of the tableView:cellForRowAtIndexPath: method for your cells implementation.
Please note that any object data needed in the loading code should be thread-safe since loading is performed in non-main thread (i.e. atomic properties and probably NSLock should be used inside the performActualFetchTableCellDataForIndexPaths: method in your time-consuming thread blocking code replacing the [NSThread sleepForTimeInterval:0.2] call).

You can download the full Xcode project from here.

#import "ViewController.h"
#import "TableViewCell.h"

static const NSUInteger kTableSizeIncrement = 20;

@interface ViewController () <UITableViewDataSource, UITableViewDelegate>

@property (nonatomic, strong) NSMutableArray* tableData;
@property (weak, nonatomic) IBOutlet UITableView *tableView;
@property (nonatomic, readonly) id tableElementPlaceholder;
@property (nonatomic, strong) NSTimer* tableDataLoadDelayTimer;

- (void)fetchTableCellDataForIndexPath:(NSIndexPath*)indexPath;
- (void)performActualFetchTableCellDataForIndexPaths:(NSArray*)indexPaths;
- (void)tableDataLoadDelayTimerFired:(NSTimer*)timer;

@end

@implementation ViewController

- (void)viewDidLoad
{
[super viewDidLoad];
self.tableData = [NSMutableArray arrayWithCapacity:kTableSizeIncrement];
for (NSUInteger i = 0; i < kTableSizeIncrement; i++) {
[self.tableData addObject:self.tableElementPlaceholder];
}
// Do any additional setup after loading the view, typically from a nib.
}

- (id)tableElementPlaceholder
{
return @"";
}

- (void)fetchTableCellDataForIndexPath:(NSIndexPath*)indexPath
{
if (self.tableView.decelerating && !self.tableView.tracking) {
if (self.tableDataLoadDelayTimer != nil) {
[self.tableDataLoadDelayTimer invalidate];
}

self.tableDataLoadDelayTimer =
[NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:@selector(tableDataLoadDelayTimerFired:)
userInfo:nil
repeats:NO];
} else {
[self performActualFetchTableCellDataForIndexPaths:@[indexPath]];
}
}

- (void)tableDataLoadDelayTimerFired:(NSTimer*)timer
{
[self.tableDataLoadDelayTimer invalidate];
self.tableDataLoadDelayTimer = nil;

NSArray* indexPathsForVisibleRows = [self.tableView indexPathsForVisibleRows];
[self performActualFetchTableCellDataForIndexPaths:indexPathsForVisibleRows];
}

- (void)performActualFetchTableCellDataForIndexPaths:(NSArray*)indexPaths
{
for (NSIndexPath* indexPath in indexPaths) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
[NSThread sleepForTimeInterval:0.2]; // emulation of time-consuming and thread-blocking operation
NSString* value = [NSString stringWithFormat:@"Text at cell #%ld", (long)indexPath.row];

dispatch_async(dispatch_get_main_queue(), ^{
self.tableData[indexPath.row] = value;
[self.tableView reloadRowsAtIndexPaths:@[indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
});
});
}
}

#pragma mark UITableViewDataSource protocol
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.tableData.count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row == (self.tableData.count - 1)) {
for (NSUInteger i = 0; i < 20; i++) {
[self.tableData addObject:self.tableElementPlaceholder];
}
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}

TableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TableViewCell" forIndexPath:indexPath];

NSString* text = [self.tableData objectAtIndex:indexPath.row];
if (text.length == 0) {
cell.activityIndicator.hidden = NO;
[cell.activityIndicator startAnimating];
cell.label.hidden = YES;
[self fetchTableCellDataForIndexPath:indexPath];
} else {
[cell.activityIndicator stopAnimating];
cell.activityIndicator.hidden = YES;
cell.label.hidden = NO;
cell.label.text = text;
}

return cell;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}

@end

For your project, the performActualFetchTableCellDataForIndexPaths: method would look as follows:

- (void)performActualFetchTableCellDataForIndexPaths:(NSArray*)indexPaths
{
for (NSIndexPath* indexPath in indexPaths) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSData* data = [DKStoreManager loadFileWithName:[finalArray objectAtIndex:indexPath.row] forFolderNumber:[self.FolderCode intValue] forUser:[self.PassedUserID intValue] andType:self.FolderType];
NSDictionary *myDictionary = (NSDictionary*) [NSKeyedUnarchiver unarchiveObjectWithData:data];

dispatch_async(dispatch_get_main_queue(), ^{
self.tableData[indexPath.row] = myDictionary;
[self.tableView reloadRowsAtIndexPaths:@[indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
});
});
}
}

Please note that you'll need to use atomic properties self.FolderCode and self.PassedUserID instead of instance variables _FolderCode and _PassedUserID, because loading file is performed in a separate thread and you need to make this data thread-safe.

As for the tableElementPlaceholder method, it might look as follows:

- (id)tableElementPlaceholder
{
return [NSNull null];
}

Correspondingly, in the tableView: cellForRowAtIndexPath: method check if data load completed would look like this:

NSObject* data = [self.tableData objectAtIndex:indexPath.row];
if (data == [NSNull null]) {
cell.activityIndicator.hidden = NO;
[cell.activityIndicator startAnimating];
cell.label.hidden = YES;
[self fetchTableCellDataForIndexPath:indexPath];
} else if ([data isKindOfClass:[NSData class]]) {
[cell.activityIndicator stopAnimating];
cell.activityIndicator.hidden = YES;
NSData* actualData = (NSData*)data;
// Initialize your cell with actualData...
}


Related Topics



Leave a reply



Submit