UITableView's AccessoryView Being Reused On Multiple Cells - ios

I have a TableView that I would like to add a checkmark to a row when it is tapped. For that, I have:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *selectedCountry = [self.files objectAtIndex:indexPath.row];
NSString *Documents = [[NSBundle mainBundle] pathForResource:selectedCountry ofType:#"ppt" inDirectory:#"thepowerpoints"];
//NSLog(#"%#", selectedCountry);
UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
if (newCell.accessoryType == UITableViewCellAccessoryNone) {
if (self.chosen == nil) {
self.chosen = [[NSMutableArray alloc] init];
}
newCell.accessoryType = UITableViewCellAccessoryCheckmark;
[self.chosen addObject:Documents];
NSLog(#"%#", self.chosen);
}else {
newCell.accessoryType = UITableViewCellAccessoryNone;
[self.chosen removeObject:Documents];
}
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
}
I understand that this is happening due to the dequeueReusableCellWithIdentifier on the type of Cell. My question is, what can I change, so that only the tapped rows get the checkmark, and not all of these other cells, once they start getting reused?

Move the code that decides whether a cell should be checked into cellForRowAtIndexPath. Keep a reference to the currently selected index/item and then set the checkmark on that cell. You would set unselected cells to have the accessory view set to none.

Related

iOS Datasource not being set as expected of the TableView

I have a TableView "cannedTv". Each cell contains another TableView "valuesTv". The structure of datasource for cannedTv is { NSString *name, NSArray *valuesArr }. valuesArr is set as datasource for valuesTv. cannedTv is an expandable tableview. Initially just name is displayed, when expanded the valuesTv tableView is displayed. This is my code for didSelectRowAtIndexPath and cellForRowAtIndexPath delegate methods of the tableviews.
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"ROW Selected...TAG = %d", tableView.tag);
selectedValueIndex = -1;
if (tableView == self.cannedTV) {
// USer taps expanded row
if (selectedIndex == indexPath.row) {
selectedIndex = -1;
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath ] withRowAnimation:UITableViewRowAnimationFade];
return;
}
// USer taps differnt row
if (selectedIndex != -1) {
NSIndexPath *prevPath = [NSIndexPath indexPathForRow:selectedIndex inSection:0];
selectedIndex = indexPath.row;
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:prevPath] withRowAnimation:UITableViewRowAnimationFade];
}
// User taps new row with none expanded
selectedIndex = indexPath.row;
[tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
return;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Table View TAG = %d ROW = %d", tableView.tag, indexPath.row);
if (tableView.tag == 10) { // cell.valuesTv
CannedValueCell *ccell = (CannedValueCell *) [tableView dequeueReusableCellWithIdentifier:#"cannedValueCell"];
if (ccell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CannedValueCell" owner:self options:nil];
ccell = [nib objectAtIndex:0];
}
// Populate valuesTv
ccell.valueTextView.text = [valuesArray objectAtIndex:indexPath.row];
UITapGestureRecognizer *gestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(valueTextViewTapped)];
[ccell.valueTextView addGestureRecognizer:gestureRecognizer];
//[ccell.valueTextView sizeToFit];
return ccell;
} else {
// cannedTv
static NSString *cellIdentifier = #"CannedCell";
cell = (CannedCell *) [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CannedCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
// Set Datasource & Delegate for valuesTv tableview
cell.valuesTv.dataSource = self;
cell.valuesTv.delegate = self;
if (selectedIndex == indexPath.row) {
// Do expanded cell stuff
[cell.tapInsertLbl setFont:[UIFont fontWithName:#"OpenSans" size:8.0]];
cell.tapInsertLbl.hidden = FALSE;
[cell.contentView setBackgroundColor:[UIColor lightGrayColor]];
[cell.contentView.layer setBorderColor: (__bridge CGColorRef)([UIColor redColor])];
[cell.contentView.layer setBorderWidth:1.0f];
} else {
// Do closed cell stuff
cell.tapInsertLbl.hidden = TRUE;
cell.contentView.backgroundColor = [UIColor whiteColor];
}
if (tableView == self.cannedTV) {
valuesArray = nil;
CannedItem *ci = [candRespList objectAtIndex:indexPath.row];
cell.tagLbl.text = ci.name;
// Set the valuesArray so it be used in populating the valuesTv
valuesArray = [NSMutableArray arrayWithArray: ci.valuesArr];
ci = nil;
} else if (tableView == self.searchDisplayController.searchResultsTableView) {
cell.tagLbl.text = [searchResults objectAtIndex:indexPath.row];
}
[cell.tagLbl sizeToFit];
return cell;
}
}
The problem I am facing is, every time the right datasource is shown shown in valuesTv. At this moment I have 2 arrays in valuesArray with 0th element having 1 object and 1st element having 3 objects (of NSString). Datasource of cannedTv also has 2 rows in it. Sometimes, one time both rows of canndTv shows correct datasource and then both shows same datasource. Sometimes, both rows shows the same datasource. I don't understand why valuesArray can't get the right datasource. While debugging also, I found that at times valuesArray has right datasource but the tableview is showing wrongly, at times the control doesn't go to // Populates valuesTv line and thus previously set valuesArray is only shown. I tried many ways, but can't get the results as expected. Also tried to set valuesArray in didSelectRowAtIndexPath after setting selectedIndex, but that also didn't help.
I am stuck on this since yesterday and can't get thru. Where am I going wrong due to which correct datasource is not shown/reflected on the tableviews. Can you please try to help me out. Any help is highly appreciated. Thanks a lot.
UPDATE :-
Created new datasource & delegate in CannedCell object itself - it contains the valuesTv.
#interface CannedCell : UITableViewCell <UITableViewDataSource, UITableViewDelegate>
#property NSArray *valuesDataSource; // Contains the valuesArray contents
In my VC, removed datasource for cell.values.datasource & cell.valuesTv.delegate lines. In the cellForRowAtIndexPath method for top table, set the datasource like this :-
if (tableView == self.cannedTV) {
valuesArray = nil;
CannedItem *ci = [candRespList objectAtIndex:indexPath.row];
cell.tagLbl.text = ci.name; //[tagsArray objectAtIndex:indexPath.row];
//valuesArray = [NSMutableArray arrayWithArray: ci.valuesArr];
cell.valuesDataSource = ci.valuesArr; // SET DATASOURCE
// This is setting proper array always
ci = nil;
}
Commented the whole if (tableView.tag == 10) { in cellForRowAtIndexPath method.
And in CannedCell :-
-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (valuesDataSource != nil)
return valuesDataSource.count;
else
return 0;
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
CannedValueCell *ccell = (CannedValueCell *) [tableView dequeueReusableCellWithIdentifier:#"cannedValueCell"];
if (ccell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CannedValueCell" owner:self options:nil];
ccell = [nib objectAtIndex:0];
}
// Populate valuesTv
ccell.valueTextView.text = [valuesDataSource objectAtIndex:indexPath.row];
ccell.valueTextView.tag = indexPath.row;
return ccell;
}
Yet the results are as earlier only. I mean even if array of 3 objects are set as valuedDataSource, the sub-table shows only single object array. What do the think now can be the reason for this ?
As suggested by Paulw11, I created other object to handle TableViewDelegate and datasource for the sub-table. Though that didn't make any difference in the results of the problem.
I set UITableViewDelegate & Datasource in my top table's Cell class. Passed the array to be the datasource of the table.
Importantly, after setting the datasource of sub-table on calling
[cell.valuesTv reloadData]
forced the sub-table to reload its data and that finally did the trick. Till then things weren't working.

Selecting UITableViewCells with CheckmarkAccessory and cell reuse identifier

Alright so I'm using a cell reuse identifier to generate UITableViewCells on my table view. This UITableView displays all of your contacts first and last names, first by grabbing them through ABAddressBook and then using the data in cellForRowAtIndexPath: like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"inviteCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// forIndexPath:indexPath]; taken from above
if(tableView == self.tableView)
{
//configure the cells for the contacts tableView
#define CHECK_NULL_STRING(str) ([str isKindOfClass:[NSNull class]] || !str)?#"":str
id p=[contactsObjects objectAtIndex:indexPath.row];
NSString *fName=(__bridge NSString *)(ABRecordCopyValue((__bridge ABRecordRef)(p), kABPersonSortByFirstName));
NSString *lName=(__bridge NSString *)(ABRecordCopyValue((__bridge ABRecordRef)(p), kABPersonSortByLastName));
cell.textLabel.text=[NSString stringWithFormat:#"%# %#",CHECK_NULL_STRING(fName),CHECK_NULL_STRING(lName)];
}
else
{
//configure the cells for the search bar controller's tableView.
#define CHECK_NULL_STRING(str) ([str isKindOfClass:[NSNull class]] || !str)?#"":str
id p=[searchResults objectAtIndex:indexPath.row];
NSString *fName=(__bridge NSString *)(ABRecordCopyValue((__bridge ABRecordRef)(p), kABPersonSortByFirstName));
NSString *lName=(__bridge NSString *)(ABRecordCopyValue((__bridge ABRecordRef)(p), kABPersonSortByLastName));
cell.textLabel.text=[NSString stringWithFormat:#"%# %#",CHECK_NULL_STRING(fName),CHECK_NULL_STRING(lName)];
}
return cell;
}
What I need to do is, select any of the cells generated like this, and also be able to deselect any of the cells. In the background, I'll be using the phone numbers associated with each name to check if they're registered in my database. But as for this UITableView I need to select any of the table cell's, and not also select every 10th cell, or whatever it is.
Put clearly: I need to keep track of what cells are checked, and I need to do this using minimal code. I may be wrong, but I believe in my case, I MUST USE cell reuse identifiers, and the cells must me "single-selected" and un-selectable. How do I do this?
If i understood correctly.. you need only a single selection on the tableview. Once a new row is selected you deselect the old one.
You can do this like so :
cellForRowAtIndexPath do some thing like this.
if(indexPath.row == 0){
cell.accessoryType = UITableViewCellAccessoryCheckmark;
self.lastSelected = indexPath;//This will keep track of the last selected cell .
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if([self.lastSelected compare:indexPath] == NSOrderedSame){
[tableView deselectRowAtIndexPath:indexPath animated:NO];//This is to remove the highlight on cell selection
return;
} else {
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
[tableView cellForRowAtIndexPath:self.lastSelected].accessoryType = UITableViewCellAccessoryNone;
[self setLastSelected:nil];//Releasing the old retain and resetting
self.lastSelected = indexPath;
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
}
Hope this helps
EDIT :
Ok now i understood your question. my edited answer is given below
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([tableView cellForRowAtIndexPath:indexPath].accessoryType == UITableViewCellAccessoryCheckmark) {
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryNone;
} else
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
}
This way you can select multiple cells and deselect any cell which was earlier selected.
The checkmark will be enabled or disabled depending on its earlier state.
Hope i have the correct understanding of what you wish to do.

Adding checkmark to selected table cell also checks another table cell

When adding a checkmark to selected table cells, im seeing check appear in other cells also.
my didSelectRowAtIndexPathCode is:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
PFObject *player = [squadListArray objectAtIndex:indexPath.row];
NSString *playerName = [player valueForKey:#"fullName"];
NSLog(#"%#", playerName);
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
selectedCell.accessoryType = UITableViewCellAccessoryCheckmark;
}
The NSLog has expect results, only showing the one selection.
Any ideas? Do you need me to show any other code?
Thanks
In your cellForRowAtIndexPath you can't be configuring the cell properly when the cell gets reused. You should always be setting (and resetting) all of the properties of the cell from your data model.
You must have a data model that is being used to tell the table view how many rows it has and what each cell should look like. During didSelectRowAtIndexPath you should be updating your data model with the selected information. Then, in cellForRowAtIndexPath, you can use the information in the data model to decide if the cell has a checkmark or not. If it does you add it, if it doesn't you explicitly remove it (to prevent it being left there if the cell was reused).
Your cell is being recycled by other rows. In the method, cellforrowatindexpath, add the following line at the end:
selectedCell.accessoryType = UITableViewCellAccessoryNone;
Cells are cached and re-used. You need to only save the fact you were selected (maybe in PFObject) and then set the accessory each time you configure a cell.
You need to explicitly tell that you don't want other cells to have the checkmark.
if ([self shouldSelectCell]) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
You could try doing the following:
Create NSMutableSet that holds the indexes of selected cells.
#property(strong, nonatomic) NSMutableSet *selectedCells;
-(NSMutableSet *)selectedCells{
if(_selectedCells){
return _selectedCells;
}
_selectedCells = [[NSMutableSet alloc]init];
return _selectedCells;
}
On didSelect update the set and select the cell:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryCheckmark;
[self.selectedCells addObject:indexPath];
[tableView selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionMiddle];
}
Remove the indexPath on didDEselect
-(void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
cell.accessoryType = UITableViewCellAccessoryNone;
[self.selectedCells removeObject:indexPath];
}
Inside the
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
update the cell as:
if([self.selectedCells containsObject:indexPath]){
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}else{
cell.accessoryType = UITableViewCellAccessoryNone;
}

iOS UITableViewCellAccessoryCheckmark Visible ob every scroll

I have a list which I have using as a check boxes. I have enable or disable Check mark on row on select. But when I scroll the list its make mark row after every 10 rows.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *oldCell = [tableView cellForRowAtIndexPath:indexPath];
if (oldCell.accessoryType == UITableViewCellAccessoryCheckmark)
{
oldCell.accessoryType = UITableViewCellAccessoryNone;
}
else
{
oldCell.accessoryType = UITableViewCellAccessoryCheckmark;
}
}
UItableView reuses the cell in every scroll so using condition as per accessory type is not a good practice. You can Create an NSMutableArray with selected items and Check as per the Condition below.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
if ([selected containsIndex:indexPath.row]) {
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
} else {
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
// Do the rest of your code
return cell;
}
in didSelectrowAtindexpath method you can Add and remove the Selected items.
Its because UITableView reuses the cell.
So, in the method cellForRowAtIndexPath, you will have to check for a particular cell (of a particular section and row), if it needs to be checked on, provide the accessory type.
If not needed for that cell, provide accessory type as none.
You need to put your logic to set accessory type for cell in cellForRowAtIndexPath, and to identify the cell to mark with check mark you can mark the object in the list in didSelectRowAtIndexPath: or manage an array of selected/unselected objects of the list here.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
if ([selectedCell accessoryType] == UITableViewCellAccessoryNone) {
[selectedCell setAccessoryType:UITableViewCellAccessoryCheckmark];
[NSMutableArray addObject:[AnotherMutableArray objectAtIndex:indexPath.row]];
} else {
[selectedCell setAccessoryType:UITableViewCellAccessoryNone];
[NSMutableArray removeObject:[AnotherMutableArray objectAtIndex:indexPath.row]];
}
[tableView deselectRowAtIndexPath:indexPath animated:NO];
}
Also in your viewDidLoad, instantiate you both mutable arrays-
yourmutableArray1 = [[NSMutableArray alloc]init];
yourmutableArray2 = [[NSMutableArray alloc]init];

iOS - how to set a checkmark in a UITableView the first time the table is loaded

I am an iOS newbie. I am using a checkmark to in a UITableView, and storing the checked value in a local database. When I load the app for the first time, I want to set the value of the checkmark depending on which value exists in the db. How would I do that? Presently this is what I do -
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
if ([indexPath compare:self.lastIndexPath] == NSOrderedSame)
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
{
cell.accessoryType = UITableViewCellAccessoryNone;
}
// Set up the cell...
NSString *cellValue = [[self countryNames] objectAtIndex:indexPath.row];
cell.textLabel.text = cellValue;
return cell;
}
and then in didSelectRowAtIndexPath -
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// some stuff
self.lastIndexPath = indexPath;
[tableView reloadData];
}
Are you asking simply how and when to set the checkmark or are you asking how to populate a table from a database (eg Core Data)?
From your code, the only data you represent is in [self countryNames] and it's not clear under what circumstances you'd want the cell to display a checkmark. Whatever that is, just check the condition and set the checkmark when you are configuring your cell for data (after your "Set up the cell..." comment).
Example if you stored the users country and checked that cell:
// get the current country name
NSString *cellValue = [[self countryNames] objectAtIndex:indexPath.row];
// configure the cell
cell.textLLabel.text = cellValue;
UITableViewCellAccessoryType accessory = UITableViewCellAccessoryNone;
if ([cellValue isEqualToString:self.usersCountry]) {
accessory = UITableViewCellAccessoryCheckmark;
}
cell.accessoryType = accessory;
If you have static table data, then you simply need to store the section and row of the selected table view cell. If you have dynamic data, then you will need to store the unique data for that selected cell in the database and compare that with the cell's content on load. When you are loading your cells in cellForRowAtIndexPath:, simply set that cell's accessory to UITableViewCellAccessoryCheckmark as well as set self.lastIndexPath = indexPath for comparison later.
Also, I typically use [indexPath isEqual:self.lastIndexPath] instead of compare:. Doesn't really make a difference either way, just for readability.

Resources