If we give same Identifier to all cells, Disappearing cell uses the memory of Appearing cell. Means content will repeat when I scroll Table-view. But If we give diff Identifier then every cell will have its own memory location and shows data perfectly.
Now suppose I have 1000 or more records to load in Table-view. If I will give different Identifiers, there will be lots of allocations in memory. So Is there any solution to show data perfectly with minimum memory allocation ?
Here is how I define cell identifier:
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier = [NSString stringWithFormat:#"%d%d",indexPath.section,indexPath.row];
UITableViewCell *Cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (Cell == nil)
{
Cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:cellIdentifier];
}
}
The problems that you are experiencing are caused by you improperly using cell identifiers. Cell identifier should be the same for all cells that you want to reuse. Take a look at this template, it should explain the correct approach:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *cellIdentifier = #"MY_CELL";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
// everything that is similar in all cells should be defined here
// like background colors, label colors, indentation etc.
}
// everything that is row specific should go here
// like label text, progress view progress etc.
return cell;
}
Btw. use camel case to name your variables, capitalized names are meant for class names.
you should clear the contents of dequeued cell like emptying labels and others. if you allocate separate memory for each cell you will easily go low memory. perfect memory management is still reusing cells.
Related
I would like to customize a simple UITableViewCell so that I run the customization only once and add values (e.g., cell title) later. My app's cell is more complex - it has subviews and uses auto layout; however, a simple example, I believe, will help in focusing on the objective.
I am using iOS 8, Xcode 6.X, Objective-C and Nibs (no storyboard) to keep it simple. I have not created a custom class for UITableViewCell. Instead, I have the following code:
- (void)viewDidLoad {
[super viewDidLoad];
//[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1; //FIXED VALUE FOR EXAMPLE'S SAKE
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 3; //FIXED VALUE FOR EXAMPLE'S SAKE
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"tableView:cellForRowAtIndexPath:");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
NSLog(#"cell == nil");
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
//CUSTOMIZING CELL THAT I WANT TO RUN ONLY ONCE
cell.backgroundColor = [UIColor redColor];
}
NSArray *numbersArray = #[#1,#2,#3];
cell.textLabel.text = [NSString stringWithFormat:#"%#", numbersArray[indexPath.row]];
return cell;
}
Which outputs:
tableView:cellForRowAtIndexPath:
cell == nil
tableView:cellForRowAtIndexPath:
cell == nil
tableView:cellForRowAtIndexPath:
cell == nil
FIRST QUESTION: Why is cell == nil run 3 times? It seems wasteful to run the customization code cell.backgroundColor = [UIColor redColor]; 3 times.
Now, when I enable:
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:#"Cell"];
And use:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
Instead of:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
I get the output:
tableView:cellForRowAtIndexPath:
tableView:cellForRowAtIndexPath:
tableView:cellForRowAtIndexPath:
SECOND QUESTION: Why isn't cell == nil run at all?
FINAL QUESTIONS: How can I make cell == nil run only once so that I format the UITableViewCell only once? Is there a better way to customize a simple cell, running the customization code only once?
Why is cell == nil run 3 times? It seems wasteful to run the customization code cell.backgroundColor = [UIColor redColor]; 3 times.
The table view most likely displays three cells at once, hence requiring three distinct cell objects.
Why isn't cell == nil run at all?
The documentation states that -dequeueReusableCellWithIdentifier:forIndexPath: always returns a valid cell if you registered the identifier previously. It basically takes care of checking if a new cell is required for you.
How can I make cell == nil run only once so that I format the UITableViewCell only once?
You don't. You will have to customize every single instance. I would recommend to use a custom subclass though, rather then messing with UITableViewCell from the outside.
The best way to do this, is to create a custom class for your cell, and do any customization that isn't dependent on the indexPath there. Usually, I do this in initWithCoder or awakeFromNib. You should register the nib in viewDidLoad; I don't see anything wrong with the code you mention in your comment to Christian's answer, unless the name of the file is wrong. It really isn't the view controller's business to be adding subviews or customizing your cell; that code belongs in the cell's class.
BTW, this doesn't keep the customization code from running multiple times. It needs to run once for each cell instance that you create, just like it does in your original code. The number of cells created will be equal to the number that fit on the screen at one time (plus one maybe).
I have a conditional breakpoint when the indexPath.section == 3 && indexPath.row == 3. The first time the breakpoint is triggered, and I run
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
The cell is the same cell as the cell at indexPath.section == 0 && indexPath.row == 0, rather than nil, which is what I would expect. I made sure the number of rows for section 3 is 4. Why isn't the cell nil?
I just give you suggestion, if you want to give restriction to reusability of cell then follow my below answer , But i would like to MENTION here that it is bad for memory management.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = [NSString stringWithFormat:#"S%1dR%1d",indexPath.section,indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
/// Put your code here.
}
/// Put your code here.
return cell;
}
UITableView does not necessarily start drawing with the first row. It could start drawing anywhere depending on which part of the view needs to be drawn at a given time.
The dequeueReusableCellWithIdentifier: method is only ever going to return nil once. After that it will forever return a cell that was previously used for some other row.
If you have two rows with incompatible cells, then you need to have a different identifier for each. Perhaps you should have an identifier for each section in your case for example.
The point is if you're drawing a thousand table view rows that look almost identical, except maybe the text is different, then you can create a single cell and then change the text but leave everything else (fonts, accessory view, etc) exactly the same as it goes through and draws each one.
Instead of [tableView dequeueReusableCellWithIdentifier:CellIdentifier];, use
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:CELL_IDENTIFIER];
...
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CELL_IDENTIFIER forIndexPath:indexPath];
with no checks for a nil cell.
I have a simple UITableView using custom UITableViewCells.
The options set on the UITableView's properties are only that the style is set to Grouped.
When I'm trying to scroll down through the different items the scroll is extremely jumpy.
I've researched this quite a bit looking at Tricks for improving iPhone UITableView scrolling performance? and a few other questions on this website. I haven't really been able to find a solution though.
EDIT ****
I use a WSDL webservice to load data into the UITableViewCells.
The cells only have a UITextView and three buttons in it.
EDIT ****
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"NavigatorCell";
NewCell *cell = (NewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"NewCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
cell.postId = [[items objectAtIndex:indexPath.row] objectForKey:#"PostID"];
cell.post.text = [[items objectAtIndex:indexPath.row] objectForKey:#"Post"];
return cell;
}
I see your NewCell is subclassed.
Don't forget to include this method into your NewCell.m
- (NSString *) reuseIdentifier
{
return #"Cell Identifier";
}
Of course #"Cell Identifier" should be the same that you use in your cellForRowAtIndexPath:.
If you fail to implement this method each cell will be generated from scratch.
Are you using a dequeReusableCellWithIdentifier? Follow the format below. Since you now mention you are loading data from the web you need to do this asynchronously to allow for smooth scrolling. To load from a webservice (asynchronously) there is a nice project just for that here
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"yourCellName";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
return cell;
}
Setting your tableview to Reuse cells is the most basic way to ensure good performance. Basically it means that instead of creating a new cell for every cell in the tableview, your tableview will recycle the cells that are off screen. The basic setup is below, and more can be learned from the apple documentation on UITableViewDelegate linked here
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *CellIdentifier = #"Cell Identifier";
CustomCellClassName *cell = (CustomCellClassName *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil){
cell = [[CustomCellClassName alloc] initWithFrame:CGRectMake(0, 0, tableView.frame.size.width, tableView.frame.size.height)];
//Do basic cell construction common to all cells of this type here
//Set background, image etc.
}
//Do specific cell construction here
return cell;
If you're loading data over the network for each cell, you'll see poor performance. Batch your data fetch, then when it's ready tell your tableview to reload itself.
Using Core Data as a temporary backing store, and an NSFetchedResultsController to retrieve the info from Core Data, will save you some work.
I'm doing a simples app using Storyboard that a have a View with a UITableView with a UITableViewCell that do the navigation to another UIView.
So a have to code to populate the cell on the table view.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"SampleCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
NSLog(#"cai no init da cell");
}
GPItem *item = [self.items objectAtIndex:indexPath.row];
cell.textLabel.text = #"Post";
cell.detailTextLabel.text = item.imageURL;
return cell;
}
I realised that the code if (cell == nil) { ... never executes so I really need to do that on uses the cell from Storyboard?
Thanks.
You are correct; that code is guaranteed to return a non-nil cell if you are using a storyboard. Also, in iOS 6, the new call dequeueReusableCellWithIdentifier:forIndexPath: never returns nil. See the discussion in my book:
http://www.apeth.com/iOSBook/ch21.html#_registering_a_cell_class
If you've declared your UITableViewCell in table view's prototype cells it's already allocated and just needs to be dequeued. If you're using a custom UITableViewCell subclass, then you must check if it's nil and allocate new entities when necessary.
Nope you don't need that code when using a cell made in your storyboard.
It is probably best to remove this code so that you crash nice and early if the identifier you gave to the cell in interface builder and the identifier you use in code ever drift. This snippet will mask this error and just provide a cell that you most likely was not intending to have.
I want to load around 6000 - 8000 rows in a UITableview. I get the data from the server using a async call and when I get the data I call
[tableView reloadData]
This is to refresh the table view . But because of some reason my app gets stuck and freezes .
When I debug , I found that cellforrowatindexpath is called 6000 times (on main thread) and
dequeueReusableCellWithIdentifier always returns null .
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath{
CDTableRowCell *cell = nil;
// Create and Resue Custom ViewCell
static NSString *CellIdentifier = #"CellIdentifier";
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// got into render/theme objec
if(cell == nil){
cell = [[CDTableRowCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
// MODIFYING CELL PROPERTIES HERE FROM AN ARRAY
// NO HTTP CALLS
}
Also, tableview starts reusing cell once I start scrolling but before that I never always create a new one.
Any clue why this strange behavior ???
try like this,
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier =#"Cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
return cell;
}
The method in your question is not a table view datasource method. The datasource method has the table view as an argument. The method you have written is one that can be used to obtain a cell from the tableView itself, not to obtain a new cell from the datasource.
I don't know how often that method is called but overriding it is almost certainly not what you want to do.
I'm guessing you have subclassed a uitableview to be its own datasource? If so, you need to have the code in your question in the datasource method tableView:cellForRowAtIndexPath:, and not override the method as you have now.