I have this little doubt about reusing UITableViewCell.
When we create UITableViewCell it kinda looks like following.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
[self configureCell:cell forIndexPath:indexPath];
}
}
- (void)configureCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.section) {/**Cell Config Code Goes Here**/}
}
So in my case, every cell in UITableView is different. And if UITableView reuses the cell the cell content is completely different.
Is it good practice to just pass CellIdentifier as nil so every time new cell is created instead given the condition that all cells are different ?
Or should I just move [self configureCell:cell forIndexPath:indexPath]; out and handle it on by my own ?
Cell reusability has its sense if you are using cell contents(same subviews) multiple time. Like you have two lables in your tableViewcell for all rows in your tableView. If you have small number of different cells. like if you have three types of cells to use multiple times in your tableView, you can use cell reusability with 3 different cell identifier.
But if you have all different cell, then its fine if you skip cell reusability.
The proper way of using reusability of the tableView is shown below.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *cellIdentifier = #"FollowerCell";
UITableViewCell *cell = (UITableViewCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell =[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
[self configureCell:cell forIndexPath:indexPath];
return cell;
}
- (void)configureCell:(UITableViewCell *)cell forIndexPath:(NSIndexPath *)indexPath
{
switch (indexPath.section) {/**Cell Config Code Goes Here**/}
}
The basic idea of reusability is that every time the similar type of cell should not be created, instead they should be reused just by updating their content.
What happens behind the scene is that there is a queue created in which these similar cells are added. Now Suppose there are 200 rows with different data but only 10 rows are visible. That in the queue only approx 14 cells will be present. Now as you will scroll the tableview up or down, this condition
UITableViewCell *cell = (UITableViewCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
checks wether the queue contains any cell, if yes a cell is fetched from the queue. Also the cells, which were earlier visible now on disappearing are added into the queue. This way everytime instead of new cells are created, the already created cells are used.
Now if you forcely make the cell = nil, than every time new cells will be created and added in the queue. Now if there are 200 data than queue will be containing 200 cells thus resulting in increase in memory size.
Hope it will help you in understanding the tableView. Happy Coding :)
I am afraid you have to move the configure cell code out of if condition to make every cell has its own content.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier];
}
[self configureCell:cell forIndexPath:indexPath];
}
While we saying reuse of UITableViewCells, we mean that we don't have to create the UIView hierarchy inside the cell every time. But you need to configure the content for different cells. Like cell.titleLabel.text = xxxx.
Meanwhile, you can use multiple reuseIdentifiers for different kind of cells. Or if you only have one such cell, you can define a cell as an attribute instance so that you don't have to create it everytime.
Related
Cell width always return 320, once reload the table then it returns proper cell width based on device(iphone/ipad).Below is the code
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *reuseIdentifier = #"testCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if(cell == nil)
{
cell = [[UITableViewCellalloc] initWithStyle:UITableViewCellStyleDefaultreuseIdentifier:reuseIdentifier];
}
cell.textLabel.text = #"test";
return cell;
}
probably it's because in first time your cell will be created via
[[UITableViewCellalloc] initWithStyle:UITableViewCellStyleDefaultreuseIdentifier:reuseIdentifier];
but after reload table your cell will be reused via
[tableView dequeueReusableCellWithIdentifier:reuseIdentifier]
with "old" sizes.
To avoid this, use correct cell identifier in Interface builder, in this case you can even remove creating cell code
[[UITableViewCellalloc] initWithStyle:UITableViewCellStyleDefaultreuseIdentifier:reuseIdentifier]
cell will be always generated from interface
I have a UITableView with custom cells. Most of the cells get drawn correctly. But some cells render for a short period (milliseconds) then the content inside the cell disappear.
For my cellForRowAtIndexPath, I have created a unique identifier for each cell for reuse. This is the identifier I use:
NSString *CellIdentifier = [NSString stringWithFormat:#"%d_%d",indexPath.section,indexPath.row];
This way I guarantee that using cellForRowAtIndexPath will return the correct cell after scrolling because the UITableView is reusing the correct cell.
My question is: What could cause a cell to render for a short time then disappear?
Could it be a dimensions issue (some cells exceed designated space)?
Thanks for any information that could help resolve this problem.
EDIT (code for cell for row) :
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *CellIdentifier = [NSString stringWithFormat:#"%d_%d",indexPath.section,indexPath.row];
CustomTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell){
[tableView registerNib:[UINib nibWithNibName:#"CustomCell" bundle:nil] forCellReuseIdentifier:CellIdentifier];
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.label.text=#"TEST":
}
return cell;
}
This is my code, which shows a problem for 14 rows of a table view. For one screen, 6 cells are visible.
When I tapped the 2nd cell, the 10 cell also has a checkmark, tap 3rd then 11th checkmarked, 1st then 9th checkmarked, tap 1st, then 8th also checkmarked...but the grey highlight does not behave like this, only one cell can be highlighted.
If I tap a cell after one is tapped on the the same screen, the one just checkmarked will be cleared for checkmark, which makes sense. However, if after I tapped one cell and then scroll the tableview lower, I can tap a cell and it shows checkmark as well, i.e. the one just checked on top screen still has a checkmark. So if I scroll up and down, and tap one cell each time after I scroll to the other side, I can put a checkmark for every cell, all checkmarks are shown.
It is really weird, and I have tried a lot of ways to solve it, but seems I have some basic understanding of UITableView missing, can anyone figure it out, please? Thanks.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *ID= #"UITableViewCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
}
cell.imageView.image = [UIImage imageNamed:#"earth.png"];
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell =[tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell =[tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryNone;
}
You're not storing the existence/absence of your checkmark in any sort of persisted data source and showing/hiding it in cellForRowAtIndexPath. Because of this, your table is reusing your cells, and it just throws it up as it found it. So if it reuses a cell that had your checkmark accessory enabled, it shows it as enabled (because you didn't specifically disable it in cellForRow)
I would keep an NSMutableArray of selected indexPath objects. Add the indexPath in didSelectRowAtIndexPath, and remove it in didDeselectRowAtIndexPath. Then, in cellForRowAtIndexPath, check if the current indexPath exists in the array and enable the checkmark if so, disable if not.
Stonz2's answer is right.
For example, you should create an object for table view data source and it should has a property to show is it checkmarked.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
...
Item *item = [self.itemArray objectAtIndex:indexPath.row];
if (item.checkmarked) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:NO];
Item *tappedItem = [self.itemArray objectAtIndex:indexPath.row];
tappedItem.checkmarked = !tappedItem.checkmarked;
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
}
I have custom cells with different buttons created, the buttons are always calling their actions correctly but my problem is that if I click quickly on a button, it does not highlight but is calling its action like I said before. I need to click slowly for a few seconds (1-2 seconds) to see the highlighted state. I think it could be because first of all it's taking the focus of the scrollview of the tableview but I don't know how to fix this problem.
Please help!
Try this code : Make the cell selection type none
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
// Configure the cell...
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
I simply don't understand how to fix this from what's out there.
It's fairly simple, I add a UITextField to my UITableViewCell. The user may type in it, then after scrolling it out and back into view the contents will be reset back to its default status.
This is to do with re-using old cells with dequeueReusableCellWithIdentifier right? I just don't understand how to fix it!
Here is my code:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
//Stop repeating cell contents
else for (UIView *view in cell.contentView.subviews) [view removeFromSuperview];
//Add cell subviews here...
}
You don't have to remove the cell contents once it is initialized they are never recreated, they are reused so your code should look like below
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
}
And I assume you want to have some controls onto your cell, in this case you can try using CustomCell which creates all the subviews on initializations.
Usually, all your initializations should be in
if (!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
//ALL INITS
}
and outside it you should update the values you added into the cell..
You need to set the inputted text back to the textfield, currently with reuse of cells, the textfield clears out the contents. You could try to store the textfield input in an nsstring property and in your cellforrow method, set the textfield text as that string, if the string has a valid value. That way, even on scrolling, the textfield will only display the user input that was stored into the nsstring property from the textfield.
Before You follow my Answer i want to tell you that following code is bad for memory management because it will create new cell for each rows of UITableView, so be careful for it.
But it is better to use, When UITableView Have Limited rows (about 50-100 may be ) then following code is helpful in your case. Use it, if it is suitable for you.
- (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;
}
If you have limited rows then this is best code for you.