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.
Related
I have a UITableView which contains so many rows.In my screen there can be UISwitch added for few rows in UITableView.Please tell me how can i do this?
Should i create a custom cell with a UISwitch & show or hide the UISwitch.
Should i add the UISwitch directly from the code in cellForRowAtIndexPath:.
Any suggestions please?
EDIT:
I have tried this code but this does not work.
SettingsCell *cell;
static NSString *CellIdentifier = #"SettingCell";
if (cell == nil)
{
cell = [tableView
dequeueReusableCellWithIdentifier:CellIdentifier
forIndexPath:indexPath];
[cell setSelectionStyle:UITableViewCellSelectionStyleDefault];
[tableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLine];
}
if(indexPath.row==2 || indexPath.row==3)
{
cell.switch_value.hidden=false;
}
else
{
cell.switch_value.hidden=true;
}
cell.label_text.text = [VALUES objectAtIndex:indexPath.row];
return cell;
If you are creating a new cell then you should use that cell for only required rows. For rest of the rows you should not take pain of hiding the switch.
If you create UISwitch directly from the code in CellForRowAtIndexPath, then you should take care while re-using the cell. If you use different identifier for both cell types, the show/hide problem will get resolved.
Both solutions work here : you can add an UISwitch in a UITableViewCell in cellForRowAtIndexPath: : be just careful to check that no previously created UISwitch exists before creating a new one.
The second solution consists in using a custom UITableViewCell subclass.
I prefer this solution because you don't have to cast your subviews or use viewWithTag: method, or even to check if your custom subview was already created.
You should create some simples subclasses, such as PickerCell, SwitchCell, TextFieldcell : thus you will have your own set of custom UITableViewCell subclasses ready to be used within all your projects.
Assuming you have registered your reuse identifier, try this :=
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *SettingCellIdentifier = #"SettingCell";
static NSString *StandardCellIdentifier = #"StandardCell";
if (indexPath.row == 2 || indexPath.row == 3) {
SettingsCell *cell = (SettingsCell *)[tableView dequeueReusableCellWithIdentifier:SettingCellIdentifier forIndexPath:indexPath];
[cell setSelectionStyle:UITableViewCellSelectionStyleDefault];
[tableView setSeparatorStyle:UITableViewCellSeparatorStyleSingleLine];
cell.label_text.text = [VALUES objectAtIndex:indexPath.row];
// Do whatever you want with the switch here
return cell;
}
else
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:StandardCellIdentifier forIndexPath:indexPath];
// instantiate a regular UITableViewCell
cell.label_text.text = [VALUES objectAtIndex:indexPath.row];
return cell;
}
}
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 custom UITableView class that I have multiple instances of in a single ViewController. What is the most elegant way to populate these different instances with unique cell data? Thanks for any help in advance!
Right now I can think of two possible solutions:
Have different table view data sources for each table. You can create the data sources in the same file as the view controller if that is important to you.
Have your tableView:cellForRowAtIndexPath: method conditionally load the cells depending on the table. You can find out which table view is calling the method with the first argument of the method. You could also use the tag property of UITableView for differentiation.
I personally prefer the first one.
The answer I wrote here:
https://stackoverflow.com/a/19568737/480415
May help you achieve this.:
You could also put 2 separate UITableViews on your UIViewController,
then handle it in the delegates/datasource methods, ie:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(tableView == _leftTableView)
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
//fill cell data here
return cell;
}
else if(tableView == _rightTableView)
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
//fill cell data here
return cell;
}
return nil;
}
Notice that the method signature tableView:cellForRowAtIndexPath: includes a reference to the tableView. You can use that to determine which tableView is requesting a cell, and return the same/different data accordingly.
in your controller, you should have your tableViews defined as a properties
#property (nonatomic, strong) UITableView *myTableView
Then, in tableView:cellForRowAtIndexPath: you can check which table is making the callback using something like:
if (tableView == self.myTableView){
//return cell
} else if (tableView == someOtherTableView) {
//return some other cell
}
Going to start off by saying I've seen these questions:
iOS: UITableView mixes up data when scrolling too fast
(custom) UITableViewCell's mixing up after scrolling
Items mixed up after scrolling in UITableView
The first and last seemed very relevant to my problem, however I am fairly certain that I have logic for each section to determine what should appear in the cell (the data), and yet they still get mixed up.
The following is the relevant code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Note: the if (cell == nil) thing is no longer required in iOS 6
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
if (closestRenter != nil)
{
NSLog(#"CLOSEST RENTER!");
[self setupCellsWithClosestRenterCell:cell atIndexPath:indexPath];
}
else
{
NSLog(#"NO CLOSEST RENTER");
[self setupCellsWithNoClosestRenterCell:cell atIndexPath:indexPath];
}
if (indexPath.section == 0)
{
for (UIView *view in cell.contentView.subviews)
{
NSLog(#"WHAT THE HECK");
[view removeFromSuperview];
}
}
return cell;
}
Relevant info here:
1) ClosestRenter is NOT nil... it exists. So the else clause should never be executed... and that is the case.
2) Within the code:
[self setupCellsWithClosestRenterCell:cell atIndexPath:indexPath];
There is a simple:
if (indexPath.section == 0)
{
cell.textLabel.text = #"PLACE HOLDER";
}
else
{
// Populate the cell with data. (creates a view (with controller etc) and loads it into the cell)
}
3) There are 2 sections at all times.
The problem is that section 0 (the first section) should have nothing more than that placeholder string. Section 1 should contain my custom subviews (in cells, which it does).
Section 0 initially has just the placeholder string, however once I scroll down (and the section is no longer visible) and scroll back up (quickly) it sometimes has a seemingly random cell from section 1 in there... what the heck? How? I'm reluctant to blame cell reuse but at this point outside of something really silly I don't know what it is.
The disturbing part here is that the cell in section 0 (there is only 1 row there) has no subviews. But when I scroll up and down fast it gets one (from section 1 apparently) and then I get the "WHAT THE HECK" log messages...
It should be worth mentioning that with the for loop (the one with the what the heck messages) does solve the problem (as it removes the unwanted subviews) but there has to be a better way. It feels wrong right now.
Any ideas?
(Feel free to mark this as a duplicate, but I'm fairly certain there is something else going on here).
Thanks.
So after a little bit of frustration, and careful analysis I found out why the cells were getting mixed up.
My assumption about cell reuse (particularly with the identifiers) was the problem.
Previously I was doing this:
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
Which is great and all, however there is a critical problem. All cells were technically the same underneath... after they are all allocated (not nil) the system could not determine which cell to reuse no matter what section it was.
This meant that any cell could be grabbed from the queue, with whatever it had in it, and stuck anywhere (despite my checks to make sure section 1 stuff went in section 1, and section 0 stuff (the fake renter) stayed in there).
The solution:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Note: the if (cell == nil) thing is no longer required in iOS 6
static NSString *CellIdentifier1 = #"Cell";
static NSString *CellIdentifier2 = #"Cell2";
UITableViewCell *cell;
if (indexPath.section == 0)
{
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier1];
}
else
{
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier1];
}
}
else
{
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier1];
}
else
{
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier2];
}
}
if (closestRenter != nil)
{
NSLog(#"CLOSEST RENTER!");
[self setupCellsWithClosestRenterCell:cell atIndexPath:indexPath];
}
else
{
NSLog(#"NO CLOSEST RENTER");
[self setupCellsWithNoClosestRenterCell:cell atIndexPath:indexPath];
}
return cell;
}
As you can see, section 0 will get its own cell identifier. As will section 1. The result is that when a cell is to be dequeued, it will check which section the indexPath is currently at and grab the correct cell.
Ugh, such a frustrating problem but now it all makes sense :)
Adding an else solved my problem.
Where I reseted any changes that were made to the cell.
if (! self.cell) {
self.cell = [[LanguageCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
self.cell.accessoryType = UITableViewCellAccessoryNone;
}
else {
self.cell.checkImage.image = NO;
}
There are a couple of ways to deal with this cell reuse problem (and that is what the problem is), and the way you're doing it now is ok. The problem is that when you scroll down and back up again, the cell that's returned for section 0 could be a cell that was previously used for section 1, so it will have any subviews you put in there.
Another way to handle this, is to create two different prototype cells in the storyboard, and return the one with a simple label for section 0, and return the other one, with any subviews added in IB, for section 1. This way, you will always get the correct type of cell for each section without having to remove any subviews -- you only need to re-populate the cell with the correct data.
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.