I'm trying to use a UITextField inside a UITableViewCell as you can see in the code below. It seems that when the tableview goes off screen some data that are supposed to be in the cells are mixed up. I would think that there is some problem going on with the method [tableView dequeueReusableCellWithIdentifier:addGroupContactCellIdentifier]; not being able to give me a "proper" cell after the tableview has gone off screen. What is the reason for this?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *addGroupContactCellIdentifier = #"AddGroupContactCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:addGroupContactCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:addGroupContactCellIdentifier];
if ([indexPath section] == 0) { // Group Name Section
cell.textLabel.text = #"Name";
UITextField *groupNameTextField = [[UITextField alloc]initWithFrame:CGRectMake(80, 10, 210, 22)];
groupNameTextField.textAlignment = UITextAlignmentLeft;
groupNameTextField.backgroundColor = [UIColor clearColor];
groupNameTextField.placeholder = #"Type Group Name";
//groupNameTextField.borderStyle = UITextBorderStyleLine;
groupNameTextField.clearButtonMode = UITextFieldViewModeWhileEditing;
groupNameTextField.returnKeyType = UIReturnKeyDone;
groupNameTextField.autocapitalizationType = UITextAutocapitalizationTypeSentences;
groupNameTextField.delegate = self;
[cell.contentView addSubview:groupNameTextField];
}
}
if ([indexPath section] == 1) { // Contacts Section
cell.textLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"name"];
cell.detailTextLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"number"];
}
cell.accessoryType = UITableViewCellAccessoryNone;
return cell;
}
UPDATE:
So I subclassed UITableViewCell but still it exhibits the same error as before. This is now my code for tableView:cellForRowAtIndexPath::
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *addGroupContactCellIdentifier = #"AddGroupContactCell";
if ([indexPath section] == 0) {
UITableViewCellWithUITextField *cell = [tableView dequeueReusableCellWithIdentifier:addGroupContactCellIdentifier];
if (cell == nil) {
//cell = [[UITableViewCellWithUITextField alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:addGroupContactCellIdentifier];
cell = [[UITableViewCellWithUITextField alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:addGroupContactCellIdentifier textFieldPlaceholder:#"Type Group Name" textFieldDelegate:self];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.text = #"Name";
// Need to set the UITableViewCell's textLabel properties otherwise they will cover the UITextField
cell.textLabel.opaque = NO;
cell.textLabel.backgroundColor = [UIColor clearColor];
return cell;
} else {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:addGroupContactCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:addGroupContactCellIdentifier];
}
cell.textLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"name"];
cell.detailTextLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"number"];
return cell;
}
}
Third EDIT (I have now 2 different reuseIdentifiers which seem to give me my wanted results):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath section] == 0) { // Group Name Section
static NSString *groupNameCellIdentifier = #"GroupNameCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:groupNameCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:groupNameCellIdentifier];
cell.textLabel.text = #"Name";
UITextField *groupNameTextField = [[UITextField alloc]initWithFrame:CGRectMake(80, 10, 210, 22)];
groupNameTextField.textAlignment = UITextAlignmentLeft;
groupNameTextField.backgroundColor = [UIColor clearColor];
groupNameTextField.placeholder = #"Type Group Name";
//groupNameTextField.borderStyle = UITextBorderStyleLine;
groupNameTextField.clearButtonMode = UITextFieldViewModeWhileEditing;
groupNameTextField.returnKeyType = UIReturnKeyDone;
groupNameTextField.autocapitalizationType = UITextAutocapitalizationTypeSentences;
groupNameTextField.delegate = self;
[cell.contentView addSubview:groupNameTextField];
}
// Customization
return cell;
} else {
static NSString *addGroupContactCellIdentifier = #"AddGroupContactCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:addGroupContactCellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:addGroupContactCellIdentifier];
}
// Customization
cell.textLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"name"];
cell.detailTextLabel.text = [[self.selectedPeoplePickerContacts objectAtIndex:[indexPath row]] objectForKey:#"number"];
return cell;
}
}
Subclassing is not necessary as some have suggested.
But you cannot use logic like "if ([indexPath section] == 0) {" inside of the "if (cell == nil) {" because that is only called the first time the cell is created, and it will be-re used at other indexes on subsequent recycles.
Instead, you need to use two different CellIdentifiers, so that cells you have set up for section zero do not get re-used at other places in the table. Put your if ([indexPath section] == 0) { before you dequeue the cell and use a different cell identifiers for section zero and subsequent section cells.
Also, make sure you do any indexpath-specific outside of the "if (cell == nil) {" so that it will be applied each time the cell is re-used not just the first time it is created.
You are right ! The problem is definitely due to the reusability feature of UITableView. Apple has done it in such a was so that you can reuse cells, and it works beautifully at a performance stand-point ! And so, when you try scrolling up and down, and indexPath values continue to be the same and your tableView gets data from the cellForRowAtIndexPath that you had defined in your class !
Solution:
You will need to subclass your UITableViewCell and add a UITextField in your -(void)layoutSubviews method.
Then you will need to reference this CustomUITableViewCell and use that to load your TableView.
A link that will help : Read this !
The values are mixed up because when you go offscreen and then reload the table again the cells are dequed from the internal table cells pool but they are not reloaded in the same order they were in the table previously. Note that this mixing will happen even if you have a table with many rows and you scroll it. The solution is to store your textfield data in a "data source" array and then configure the cell.
EXPLANATION
Basically in your code there is one main conceptual flaw: once you have regenerated the cell, you don't configure the content properly (you don't configure it at all). What I mean is that initially, when the table is displayed the first time, the pool is empty. So each new cell that needs to be displayed is recreated from scratch (not dequed from the pool); let's say your table can show 10 cells on screen, so the first 10 cells will be all created from scratch with empty text fields.
Then you start entering text in these fields, and all works correctly.
At a certain point you start scrolling the cell: what happens is that all cells that are in the top will disappear from screen and stored (queued) in the table pool, with their textfield and its edited content; let's say you queue cell at row 0. When a new cell needs to be displayed on bottom of the screen the first thing your code does is to try to deque a cell. Now this time you have a cell in the pool (the cell that was at row 0), this cell is retrieved from the pool and placed in the table, INCLUDED THE TEXTFIELD CONTENT, at row 11. So "magically" you will find a text edited at row 0 in another row, 11. Besides the cells are retrieved in a sparse order from the pool, so after many textfield editings and scrollings you will have a complete mixup.
Solution, and this is the reason of the bug in your code: as soon as the cell has been created or dequed, configure it, that is set the textfield content. How to retrieve the textfield content? store in an array. This is why your view controller is a "data source", because you source data to fill the table. Storing data in the table is a mistake, due to this dequeing mechanism. Example:
groupNameTextField.text=[myTextFieldContentArray objectAtIndex:indexPath.row];
Another solution, but I don't suggest it, is to assign a unique identifier to each cell, that is:
NSString *myCellId = [NSString stringWithFormat:#"CellID_%d_%d",indexPath.section,indexPath.row];
In this case all cells will be enqued with a different name and you will never mix up them.
This solution is working most of the time but it is discouraged for two reasons:
a. it is non-optimal, as you don't reuse cells and so this takes extra memory for similar cells
b. you're not guaranteed that each cell is effectively queued, all in all this logic is inside the table and it's not exposed to the developer, so it may happen that you need to re-generate each time the cell when needed (performance loss).
Related
Im working on Multiple checkbox Functionality in tableview, I have to search in google finally i got it one solution.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
if ([selectedRowsArray containsObject:[contentArray objectAtIndex:indexPath.row]]) {
cell.imageView.image = [UIImage imageNamed:#"checked.png"];
}
else {
cell.imageView.image = [UIImage imageNamed:#"unchecked.png"];
}
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleChecking:)];
[cell.imageView addGestureRecognizer:tap];
cell.imageView.userInteractionEnabled = YES; //added based on #John 's comment
//[tap release];
cell.textLabel.text = [contentArray objectAtIndex:indexPath.row];
return cell;
}
- (void) handleChecking:(UITapGestureRecognizer *)tapRecognizer {
CGPoint tapLocation = [tapRecognizer locationInView:self.tableView];
NSIndexPath *tappedIndexPath = [self.tableView indexPathForRowAtPoint:tapLocation];
if ([selectedRowsArray containsObject:[contentArray objectAtIndex:tappedIndexPath.row]]) {
[selectedRowsArray removeObject:[contentArray objectAtIndex:tappedIndexPath.row]];
}
else {
[selectedRowsArray addObject:[contentArray objectAtIndex:tappedIndexPath.row]];
}
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:tappedIndexPath] withRowAnimation: UITableViewRowAnimationFade];
}
In that code i have one doubt. what is selectedRowsArray and what is ContentArray Please explain anyone. Thanks in advance.
The selectedRowsArray is another array maintained beside your contentArray which is used to hold the content for of the cells that are currently selected in the UITableView. The contentArray holds the actual content which is populated the cells.
Make note of the following lines:
if ([selectedRowsArray containsObject:[contentArray objectAtIndex:indexPath.row]])
{
cell.imageView.image = [UIImage imageNamed:#"checked.png"];
}
else
{
cell.imageView.image = [UIImage imageNamed:#"unchecked.png"];
}
The cell contents are checked for their presence in the selectedRowsArray which means that the cell is selected.
The selection and deselection is handled by the handleChecking: method which adds or removes the object from the same index in the contentArray into the selectedRowsArray.
You must have understood by now that cells in a UITableView are reused, so the actual number of UITableViewCells in the memory are most probably less than the number of cells to be shown in the UITableView. Hence the need for a system which can actually determine whether the cell is currently selected so this status can be populated when the cell is rendered again in the view.
Alternative methods to achieve the same involve using Booleans in an array representing the status of each cell, or simply the indexes of each selected cell in an array.
I am making a form within a grouped tableview. In this form I have UIswitches and textfields. But after scrolling down, the cells styles are changing.
Here is my cellForRowAtIndex
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
static NSString *MyIdentifier = #"GenericCell";
cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] ;
}
NSString *text = nil;
if(indexPath.section == CREDENTIALS_SECTION){
if (indexPath.row == 0) {
NSLog(#"tot hier login");
UITextField *login = [[UITextField alloc] initWithFrame:CGRectMake(110, 10, 185, 30)];
login.adjustsFontSizeToFitWidth = YES;
login.placeholder = #"example#gmail.com";
login.keyboardType = UIKeyboardTypeEmailAddress;
login.returnKeyType = UIReturnKeyNext;
login.backgroundColor = [UIColor clearColor];
login.tag = 0;
login.delegate = self;
[login setEnabled: YES];
[cell addSubview:login];
}else if (indexPath.row == 1){
NSLog(#"tot hier pass");
UITextField *pass = [[UITextField alloc] initWithFrame:CGRectMake(110, 10, 185, 30)];
pass.adjustsFontSizeToFitWidth = YES;
pass.placeholder = #"Required";
pass.keyboardType = UIKeyboardTypeDefault;
pass.returnKeyType = UIReturnKeyDone;
pass.secureTextEntry = YES;
pass.backgroundColor = [UIColor clearColor];
pass.tag = 0;
pass.delegate = self;
[cell addSubview:pass];
}
if (indexPath.row == 0) { // Email
text = #"Email";
}
else if(indexPath.row == 1) {
text = #"Password";
}
}else if(indexPath.section == METHODS_SECTION){
UISwitch *toggleSwitch = [[UISwitch alloc]initWithFrame:CGRectMake(220, 10, 100, 30)];
toggleSwitch.tag = indexPath.row;
[toggleSwitch addTarget:self action:#selector(toggleSwitched:) forControlEvents:UIControlEventValueChanged];
[cell addSubview:toggleSwitch];
if (indexPath.row == 0) { // Web
text = #"Web applicatie";
}
else if(indexPath.row == 1) { //Mobile
text = #"Mobiele applicatie";
}
else if(indexPath.row == 2) { //Mail
text = #"E-mail";
}
}else if(indexPath.section == PHONE_SECTION){
UITextField *phoneText = [[UITextField alloc] initWithFrame:CGRectMake(20, 10, 185, 30)];
phoneText.adjustsFontSizeToFitWidth = YES;
phoneText.font = [UIFont fontWithName:#"Arial-BoldMT" size:18];
phoneText.keyboardType = UIKeyboardTypeNumberPad;
phoneText.delegate = self;
phoneText.textColor = [UIColor blackColor];
phoneText.text = _person.phone;
[cell addSubview:phoneText];
}else if(indexPath.section == REMARK_SECTION){
UITextView *textView = [[UITextView alloc]initWithFrame:CGRectMake(20, 10, 280, 260)];
textView.text = _person.remark;
textView.delegate = self;
textView.font = [UIFont fontWithName:#"Arial" size:15.0];
textView.backgroundColor = [UIColor clearColor];
[cell addSubview:textView];
text = #"";
}else if(indexPath.section == BUTTON_SECTION){
cell.backgroundColor = [UIColor redColor];
text = #"test";
}
cell.textLabel.text = text;
return cell;
}
After some searching I found that more people are having this problem. And that the problem lays in this piece of code.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = nil;
static NSString *MyIdentifier = #"GenericCell";
cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] ;
}
NSString *text = nil;
But I don't find a solution for it.
Hope anybody can help!
Kind regards!
Clarification
Oké so here you see a screenshot of my form. below I have a red cell (save button) when I scroll down other cells are getting a red background. And some cells, text property's are changing.
That is not gong to work. Aparently you did not yet fully understand how the re-use mechanism works.
What do you do?
First you fetch a cell to be re-used. If you get one -fine so far but the problem comes later. If you don't get one then you create a new one.
When you have created a new one, which is the case at start before the user begins scrolling, then you add some UIItems depending on section and row. I will explain why this is not actually a smart thing to do.
Then the user scrolls. Cells will dissappear from screen and then made available for re-use. Then you will fetch the cells for re-use. But it may well happen that those cells already have additional UI-Items on them because you have used them before in that way. In the following process you will add new UI Items regardless whether there are already additional UI-Items on that very cell.
What can you do:
Create your own custom table cell subclasses. One subclass for each set of additional ui items that you may need. That is probably the neatest way of doing it. For each subclass use a different re-use identifier (!!!)
This is what I would recommend!
However, there are alternatives:
You could still live with your concept but invent an individual type of re-use identfier for each type of cell that has some type of additional ui item on it. If so, then make sure that these UI items are only created and added as sub-views in the if (cell == nil) branch of your code. Only create them once and then re-use them. Cell reuse-IDs could be "email-display", "email-input" , "password-display", "password-input", "switch", ...
A variance of the solution above would be, to calculate row and section
into the reuse-identifier. Such as "cell-id-0.2" for section 0 and
row 2 - or so. But still you will have to make sure that you really
re-use the additional UI views and do not re-create them every time
when the cell is filled with data. Plus, the layout in your first section varies depending on whether you want to input password and e-mail or just display them. You will still have to deal with those variations.
If cell == nil - meaning if a cell is re-used - then first clean it from every UI item that you may have added before. You can do that by tagging your UIViews with - let's say 99 - (anything different from 0 should do) upon creation and when reusing enumerate over all subviews and remove those, which have the tag 99. Despite that you can stick with the code that you have already made.
The easiest fix is:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"GenericCell"] ;
//some more code
return cell;
}
This would remove the reusability from the tableview, but since it's a limited settings view, it can be ok. I would still advice taking 1 or 2 from Hermann Klecker's solutions.
If you also need to persist UIControl state then use
static NSString *MyIdentifier = [NSString stringWithFormat:#"GenericCell%d",indexPath.row];
It will always return your unique table row and you can use it as required.
Try to remove all subviews from cell before reusing it. Try the code :
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] ;
}
else
{
[cell.contentView.subviews makeObjectsPerformSelector:#selector(removeFromSuperview)];
}
Remove all subviews before adding the subviews on cell.
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:SimpleTableIdentifier]autorelease];
}
else
{
//To remove the subview of cell.
for (UIView *vwSubviews in [cell.contentView subviews])
{
[vwSubviews removeFromSuperview];
}
}
It may solves your problem.
Actually you have some bad code here.
In the mehthod
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
Unless it is not in if (cell == nil), you should NOT initialize and use any
-(void)addSubview:(UIView*)view
Why?
The cells are views which are reused from tableview. So If you add some subview, next time while reusing the cell, it will be added more subviews on it. Simply they are overlapped and may cause MEMORY LEAK.
Do not forget that cells are reusable. So;
if I have the following code unless I do not set text somewhere else. It is expected to all cells has the text in their text labels "this is a text". Because they are reusable.
if (someChangingBool) {
cell.textLabel.text = #"this is a text";
}
So I need to have an else for that if which sets the text something else.
For more Information.
This is probably caused by poor design, but when I scroll my table too fast, and then scroll back to the top, the view that is placed in the last table cell is also placed over the first table cell in the tableView.
I think it is probably caused by my use of if statements to put some static content in the first section and dynamic content in the second section.
Any help is appreciated.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
if (indexPath.section == 0) {
if (indexPath.row == 0) {
cell.selectionStyle = UITableViewCellSelectionStyleNone;
UITextField *textField = [[UITextField alloc]initWithFrame:CGRectMake(10, 13, 282, 20)];
textField.clearsOnBeginEditing = NO;
textField.placeholder = #"enter template name";
textField.font = [UIFont systemFontOfSize:15.0];
textField.clearButtonMode = UITextFieldViewModeWhileEditing;
textField.delegate = self;
textField.text = [selectedTemplate name];
[textField addTarget:self action:#selector(nameDidChange:) forControlEvents:UIControlEventEditingChanged];
[cell.contentView addSubview:textField];
} else {
cell.textLabel.text = #"Add a new question";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
} else {
NSString *label = [[sortedQuestions valueForKey:#"questionText"] objectAtIndex:indexPath.row];
CGSize stringSize = [label sizeWithFont:[UIFont boldSystemFontOfSize:15] constrainedToSize:CGSizeMake(230, 9999) lineBreakMode:NSLineBreakByWordWrapping];
UITextView *textV=[[UITextView alloc] initWithFrame:CGRectMake(5, 5, 230, stringSize.height+10)];
textV.font = [UIFont systemFontOfSize:15.0];
textV.text=label;
textV.textColor=[UIColor blackColor];
textV.editable = NO;
textV.userInteractionEnabled = NO;
textV.backgroundColor = [UIColor colorWithRed:0.97f green:0.97f blue:0.97f alpha:1.00f];
[cell.contentView addSubview:textV];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
return cell;
}
Here is what I ended up doing based on Wienke's recommendation. I added 3 prototype cells to my storyboard named Cell,Cell2,Cell3.
Relevant code:
static NSString *CellIdentifier = #"Cell";
static NSString *CellIdentifier2 = #"Cell2";
static NSString *CellIdentifier3 = #"Cell3";
UITableViewCell *cell;
if (indexPath.section == 0 && indexPath.row == 0) {
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
} else if (indexPath.section == 0 && indexPath.row == 1) {
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier2 forIndexPath:indexPath];
} else if (indexPath.section == 1) {
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier3 forIndexPath:indexPath];
}
I also added this to check my dynamic cells and remove any lingering subviews that could be hanging around before adding the new subview.
if ([cell.contentView subviews]){
for (UIView *subview in [cell.contentView subviews]) {
[subview removeFromSuperview];
}
}
I know this already has an accepted answer, but I figured I would go slightly deeper into the "why" this was happening, and the solution.
In layman's terms, the UITableView refreshing was lagging with the presentation speed asked. In other words, due to the rapid scroll, the table view was expected to repopulate the content faster than it could sort through the expected cell to reuse for it's respective index (row).
The simplest solution, which Wienke touched on, is to create different cell identifiers for the first, let's say three (3) cells. This will allow the table view to be able to clearly differentiate between the 3 cells, preventing any cell misplacement.
Perhaps the best approach here would be to assign relevant cell identifiers to each cell (depending on the context and the number of cells). This way the table view knows precisely which cell (with it's respective ID) goes where. Something as simple as the following:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *cellID = [NSString stringWithFormat:#"cell%lu", indexPath.row];
__kindof UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellID];
if (!cell) {
// Create your cell here.
cell = [...allocate a new cell...] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
}
return cell;
}
It looks like a cell that was layed out for, say, section 0 row 0 is being dequeued and used for, say, section 0 row 1, which does not replace all of the settings that were made for it when it was holding section 0 row 0 material.
You might want to consider using 3 separate cell identifiers, one for section 0 row 0, one for section 0 row 1, and one for all the rest. You'll need a preliminary set of if statements (or switch-case statements) so that you use the right identifier when calling dequeueReusableCellWithIdentifier:.
I have, in my main tableView, some custom cells (cells with an imageView, basically).
The imageView is set only if a value on my plist is false. Then, when it's true, the imageView is nil.
Basically when the user enters the detailView, the value is set to YES and the cell.imageView is nil.
And it's okay, it works
I'm using a searchDisplayController, when i search for something that has a cell.imageView, going into the detailView and then coming back to the searchResultsTable, the cell has still the image, while it shouldn't, but the main tableView has the correct cell (so, with no image).
I thought that it could depend on searchResultsTableView, but i'm not sure.
I tried with
[self.searchDisplayController.searchResultsTableView reloadData];
with no effect.
How could i reload the searchResultsTableView so that it shows the right cells, those with the image and those that don't have the image anymore?
Any help appreciated!
EDIT
This is my cellForRowAtIndexPath method:
- (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] autorelease];
}
NSArray *rows;
if (tableView == self.searchDisplayController.searchResultsTableView) {
rows = filteredList; //for search
} else {
NSDictionary *section = [localSortedTips objectAtIndex:indexPath.section];
rows = [section objectForKey:#"Rows"];
}
NSDictionary *item = [rows objectAtIndex:indexPath.row];
cell.textLabel.text = [item objectForKey:#"name"];
if ([[item valueForKey:#"isRead"] boolValue] == NO) {
cell.imageView.image = [UIImage imageNamed:#"unread.png"];
} else {
cell.imageView.image = nil;
}
cell.textLabel.font = [UIFont boldSystemFontOfSize:15.0];
cell.textLabel.adjustsFontSizeToFitWidth = YES;
return cell;
}
If I understood you right, then you can have a workaround but searching again with the same search string:
if (self.searchDisplayController.active) {
self.searchDisplayController.searchBar.text = self.searchDisplayController.searchBar.text;
}
put it in viewWillAppear: or viewDidAppear: which will be called each time the view is shown up (eg. you go back from the detail view to your search view). And reloading the data in this place would be nice too, to get the right data (for example if you marked the cell as read like in your sample code)
Just [self.tableView reloadData]; and not the searchResultsTableView (it will be automatically use the updated data after the new search)
It sounds as if perhaps your cells are being recycled and not reset properly. UITableView uses view recycling, so it's important that if you do something such as set an image you make sure it is explicitly set even when their isn't an image to display.
If you share your cellsForRowAtIndexPath code you might be able to get some more help.
I have a UITableView populated with 27 rows. I am trying to change the accessoryType of the selected cell. I do that in the didSelectRowAtIndexPath: delegate method.
The problem I am facing is that, when selecting a row and changing the accessoryType of the cell, the eleventh row from that row also gets modified.
I have tried printing the [indexPath row] value, but it's showing only the row that was selected and not another one.
I am really puzzled by such stuff; please help me out.
ADDED THE CODE cellForRowAtIndexPath method
UITableViewCell *cell;
if ([indexPath row] == 0) {
cell = [tableView dequeueReusableCellWithIdentifier:#"acell"];
}
else {
cell = [tableView dequeueReusableCellWithIdentifier:#"bcell"];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
if (cell == nil && [indexPath row] != 0) {
cell = [[[UITableViewCell alloc] initWithStyle:
UITableViewCellStyleSubtitle reuseIdentifier:#"bcell"] autorelease];
}
else if(cell == nil && [indexPath row] == 0){
cell = [[[UITableViewCell alloc] initWithStyle:
UITableViewCellStyleSubtitle reuseIdentifier:#"acell"] autorelease];
}
if ([cell.contentView subviews]){
for (UIView *subview in [cell.contentView subviews]) {
[subview removeFromSuperview];
}
}
if ([indexPath row] == 0) {
cell.textLabel.text = #"Select All";
cell.textLabel.font = [UIFont boldSystemFontOfSize:13.0f];
}
else {
cell.textLabel.text = #"Some Text Here"
cell.detailTextLabel.text = #"Another piece of text here"
}
return cell;
I am doing %10 because the behaviour is repeating after 11th row, hence trying to create new object for every 11th row.
My didSelectRowAtIndexPath methos code is
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if (cell.accessoryType == UITableViewCellAccessoryCheckmark) {
if ([indexPath row] != 0) {
NSIndexPath *tempIndex = [NSIndexPath indexPathForRow:0 inSection:0];
UITableViewCell *tempCell = [tableView cellForRowAtIndexPath:tempIndex];
tempCell.accessoryType = UITableViewCellAccessoryNone;
}
cell.accessoryType = UITableViewCellAccessoryNone;
}
else{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
if ([indexPath row] == 0) {
for (int i = 0; i < [dataSource count]; i++) {
NSIndexPath *tempIndex = [NSIndexPath indexPathForRow:i+1 inSection:0];
UITableViewCell *tempCell = [tableView cellForRowAtIndexPath:tempIndex];
if (cell.accessoryType == UITableViewCellAccessoryCheckmark) {
tempCell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else{
tempCell.accessoryType = UITableViewCellAccessoryNone;
}
}
}
Please help me in multiple selection or anyother way to solve the problem of multiple selection.
Thanks in advance!!
Here's one way to do it:
- (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] autorelease];
}
[cell.textLabel setText:[NSString stringWithFormat:#"Row %d", indexPath.row]];
NSIndexPath* selection = [tableView indexPathForSelectedRow];
if (selection && selection.row == indexPath.row) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
// Configure the cell.
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
}
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryNone;
}
Remember every cell in the table view is actually the same object being re-used. If you don't set the accessory type every time cellForRowAtIndexPath is called, when new cells scroll onto the screen they're going to all have the same accessory.
Multiple Select
For multiple selection it's a bit more complicated.
Your first option: Undocumented API
Note that this only works when the table's in editing mode. Set each cell's editing style to the undocumented UITableViewCellEditingStyleMultiSelect. Once you do that, you can get the table view's selection via an undocumented member of UITableView: indexPathsForSelectedRows. That should return an array of the selected cells.
You can expose this bit of functionality by putting this in a header:
enum {
UITableViewCellEditingStyleMultiSelect = 3,
};
#interface UITableView (undocumented)
- (NSArray *)indexPathsForSelectedRows;
#end
Then set the editing style for each cell like so:
- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewCellEditingStyleMultiSelect;
}
When the table is in editing mode you'll see the multi-select controls on your cells.
To look through other undocumented API, you can use the nm command line utility like this:
nm /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS4.3.sdk/System/Library/Frameworks/UIKit.framework/UIKit
Your second option: Manage the selection yourself
Have your UITableView subclass contain an array that indicates which cells are selected. Then in cellForRowAtIndexPath, configure the cell's appearance using that array. Your didSelectRowAtIndexPath method should then look something like this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([tableView indexPathIsSelected:indexPath]) {
[tableView removeIndexPathFromSelection:indexPath];
} else {
[tableView addIndexPathToSelection:indexPath];
}
// Update the cell's appearance somewhere here
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
This assumes you create indexPathIsSelected, removeIndexPathFromSelection, and addIndexPathToSelection methods in your UITableView subclass. These methods should do exactly what their names imply: Add, remove, and check for index paths in an array. You wouldn't need a didDeselectRowAtIndexPath implementation if you go with this option.
Remember every cell in the table view is actually the same object being re-used. If you don't set the accessory type every time cellForRowAtIndexPath is called, when new cells scroll onto the screen they're going to all have the same accessory." - daxnitro
This is where I got caught. I had mine set up so that in my "cellForRowAtIndexPath" function, I would only change the accessory for those specified in my array of checked cells, when what I should have done was update the accessory for all cells in the table.
In other words:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//normal set up
//retrieve key
NSUserDefaults *settings = [NSUserDefaults standardUserDefaults];
id obj = [settings objectForKey:#yourKey];
//if the array is not populated, keep standard format for all cells
if (obj == nil){
selectedStyles = [[NSMutableArray alloc] initWithObjects:nil];
[cell setAccessoryType:UITableViewCellAccessoryNone]; //no check mark
[cell textLabel].textColor = [[UIColor alloc] initWithRed:0.0/255 green:0.0/255 blue:0.0/255 alpha:1.0]; //keep black color
}
//else retrieve information from the array and update the cell's accessory
else{
//if the cell is in your array, add a checkbox
[cell setAccessoryType:UITableViewCellAccessoryCheckmark]; //add check box
[cell textLabel].textColor = [[UIColor alloc] initWithRed:50.0/255 green:79.0/255 blue:133.0/255 alpha:1.0]; //change color of text label
//if the cell is not in your array, then keep standard format
[cell setAccessoryType:UITableViewCellAccessoryNone]; //no check mark
[cell textLabel].textColor = [[UIColor alloc] initWithRed:0.0/255 green:0.0/255 blue:0.0/255 alpha:1.0]; //keep black color
Hope this helps!