All I'm looking to do is get the selected (checkmarked) rows from my UITableView and show them in my console log. Doesn't seem like it should be so difficult. I've found two methods that I'll display below. Neither work despite the logic mostly making sense to me. Which would you suggest and how can I tweak to make it work?
My TableView Code:
I don't think this is completely necessary to the issue, but I know it sometimes helps to see the whole picture.
#pragma mark - tableView datasource
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [self.places count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
NSDictionary *tempDictionary= [self.places objectAtIndex:indexPath.row];
cell.textLabel.text = [tempDictionary objectForKey:#"name"];
if([tempDictionary objectForKey:#"vicinity"] != NULL)
{
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#",[tempDictionary objectForKey:#"vicinity"]];
}
else
{
cell.detailTextLabel.text = [NSString stringWithFormat:#"Address Not Available"];
}
return cell;
}
//Handles tableView row selection and addition and removal of the checkmark
- (void)tableView:(UITableView *)theTableView
didSelectRowAtIndexPath:(NSIndexPath *)newIndexPath {
[theTableView deselectRowAtIndexPath:[theTableView indexPathForSelectedRow] animated:NO];
UITableViewCell *cell = [theTableView cellForRowAtIndexPath:newIndexPath];
if (cell.accessoryType == UITableViewCellAccessoryNone) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
// Reflect selection in data model
} else if (cell.accessoryType == UITableViewCellAccessoryCheckmark) {
cell.accessoryType = UITableViewCellAccessoryNone;
// Reflect deselection in data model
}
}
Method 1:
Add a conditional statement to the end of the checkmark handler to add/remove selections to and from an array. Then create a button action that simply calls the array and displays it in the console. I think this is clunky but could work.
//Handles tableView row selection and addition and removal of the checkmark
- (void)tableView:(UITableView *)theTableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[theTableView deselectRowAtIndexPath:[theTableView indexPathForSelectedRow] animated:NO];
UITableViewCell *cell = [theTableView cellForRowAtIndexPath:indexPath];
if (cell.accessoryType == UITableViewCellAccessoryNone) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
//Reflect selection in data model
} else if (cell.accessoryType == UITableViewCellAccessoryCheckmark) {
cell.accessoryType = UITableViewCellAccessoryNone;
//Reflect deselection in data model
}
if ([[theTableView cellForRowAtIndexPath:indexPath] accessoryType] == UITableViewCellAccessoryCheckmark) {
[_selectedCellIndexes addObject:indexPath];
}
}
- (IBAction)sendResults:(id)sender {
NSLog(#"Add these: %#", _selectedCellIndexes);
}
Method 2:
Get the selected rows AND send to console log only when button is tapped. This seems to be the more logical method, but I can't seem to get it to work either. It doesn't throw any errors, but returns "Selected Items: (null)" in the console. What have I missed?
//Sends checkmarked items to console log
- (IBAction)sendResultsOption1:(id)sender {
NSMutableArray *aList = [[NSMutableArray alloc] init];
for (NSIndexPath *indexPath in _tableView.indexPathsForSelectedRows) {
NSString *r = [NSString stringWithFormat:#"%li",(long)indexPath.row];
[aList addObject:r];
}
NSLog(#"Selected Items: %#", _aList);
}
For what it's worth, I've also followed the instructions here without any luck. Hope you guys can help. Thanks in advance!
In Method 1, your method looks like this:
- (void)tableView:(UITableView *)theTableView
didSelectRowAtIndexPath:(NSIndexPath *)newIndexPath {
But you're referring to indexPath in the body. You don't have an indexPath (the undeclared identifier), but you have a newIndexPath, so at a minimum, this is the start of your problems and should be fixed first.
Giving your variables the right names looks like it should work for this approach...
In Method 2, the problem is none of your table view cells are selected. In you didSelectRowAtIndexPath method, you check the accessory icon to a check mark, then you deselect the row. So there are no objects in _tableView.indexPathsForSelectedRows.
In this approach, you need to change your for loop. Instead you need to iterate through every index path, and check on the accessory icon. If it's a check mark, add it to the array. Now log the array.
As far as which approach would be preferable, it depends on how you intend to use this ultimately. Obviously, the end goal isn't to NSLog the checkmarked rows--this is an iOS app we're talking about.
Related
So I am having a really weird issue. My code works great on iOS 8 but doesn't on iOS 7 and I can't figure out why.
I have a tableview which has a list of items which when you select an item a checkmark is added to that item and if you select it again the checkmark is removed. Pretty simple right? :-p Like I said it works great on iOS 8, but when I run against iOS 7.1 the cell highlights, adds a checkmark, and removes the old title and replaces it with the default of Title. Afterwards, no matter how many times I tap on the cell, it never changes (but the underlying data does change).
Before Selection
After Selection
If I leave the screen and come back to it, the rows are displayed properly. I've verified that cellForRowAtIndexPath is being called and the correct values are being added to the cell.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return [[parkFinderSingleton.data valueForKey:#"amenities"] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"amenityFilterCell" forIndexPath:indexPath];
Amenity *currentAmenity;
NSArray *amenities = [parkFinderSingleton.data valueForKey:#"amenities"];
if (amenities != nil) {
currentAmenity = amenities[indexPath.row];
cell.textLabel.text = currentAmenity.amenityTitle;
NSLog(#"Cell Title %#", cell.textLabel.text);
if (currentAmenity.amenitySelected) {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"amenityFilterCell" forIndexPath:indexPath];
Amenity *currentAmenity;
NSArray *amenities = [parkFinderSingleton.data valueForKey:#"amenities"];
if (amenities != nil) {
currentAmenity = amenities[indexPath.row];
if (currentAmenity.amenitySelected) {
currentAmenity.amenitySelected = NO;
cell.accessoryType = UITableViewCellAccessoryNone;
} else {
currentAmenity.amenitySelected = YES;
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
}
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
}
Any thoughts as to what might be happening?
Normally, dequeueReusableCellWithIdentifier:forIndexPath: should not be called in tableView: didSelectRowAtIndexPath:. If you want the cell for a specific indexPath, use [tableView cellForRowAtIndexPath:indexPath].
For:
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
If you just want to deselect a cell, use [tableView deselectRowAtIndexPath:animated:].
i'm creating a tableview which contain city names which i retrieve from a parse database. I've implemented multiple selection using the didSelectRowAtIndexPath and cellForRowAtIndexPath delegate methods. When a user is done selected cities i want to save them in the parse database, but when the user come back i want it to have the selected cities selected from the beginning.
How can i implement this? Do i need to save the indexPath in my parse database and then retrieve it and then compare it to the indexPaths in the tableView or a better solution?
cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UserCell *cell = (UserCell *) [tableView dequeueReusableCellWithIdentifier:kCellIdentifier forIndexPath:indexPath];;
if (tableView == self.searchDisplayController.searchResultsTableView) {
cell.textLabel.text = [[self.filteredCandyArray objectAtIndex:indexPath.row] objectForKey:#"name"];
} else {
cell.textLabel.text = [[cityArray objectAtIndex:indexPath.row] objectForKey:#"name"];
}
if ([cellSelected containsObject:indexPath])
{
cell.accessoryType = UITableViewCellAccessoryCheckmark;
}
else
{
cell.accessoryType = UITableViewCellAccessoryNone;
}
return cell;
}
didSelectRowAtIndexPath
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
//if you want only one cell to be selected use a local NSIndexPath property instead of array. and use the code below
//self.selectedIndexPath = indexPath;
//the below code will allow multiple selection
if ([cellSelected containsObject:indexPath])
{
[cellSelected removeObject:indexPath];
}
else
{
[cellSelected addObject:indexPath];
}
[tableView reloadData];
}
Instead of saving NSIndexPaths in cellSelected, save city names. Then once you come back to this tableview controller again, you can simply fetch selected city names and populate the cellSelected array
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.
What I am trying to achieve:
I have a UITableView and I want to check whether the table was selected or not and keep in an array easy to access the YES or NO values that corresponds to that row so that afterwards i can manipulate the data.
my code as follows
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSUInteger row = [indexPath row];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString *cellLabelText = cell.textLabel.text;
if (cell.accessoryType == UITableViewCellAccessoryCheckmark) {
cell.accessoryType = UITableViewCellAccessoryNone;
selected[row] = NO;
}
else {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
selected[row] = YES;
}
}
As it stands out I can create a BOOL selected[some value] but my problem is that the max index needed for me is unknown as my table size changes constantly. thus setting the max index limits me.
I am new to objective C and I come from a PHP background thus I dont know whether it is possible to create an array that does what i want to do in objective-c.
Otherwise what would be my options within objective-c to have an easy way to easy write/read selected[row] = YES/NO.
I need a way to write YES/NO and link it to the indexpath.row
Use an NSMutableSet and store the NSIndexPath of the selected rows. If you select a row you add the path to the set. If you unselect a row, remove the path from the set.
To see if a row is selected, see if the indexPath is in the set or not.
BTW - this only works if the rows are fixed. If the user can add, remove, or reorder rows then this approach will not work. In such a case you need to store data keys, not index paths.
Create an ivar of type NSMutableSet. Let's call it selectedRows:
selectedRows = [[NSMutableSet alloc] init];
Then in didSelectRow you do:
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
BOOL selected = [selectedRows containsObject:indexPath];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSString *cellLabelText = cell.textLabel.text;
if (selected) {
cell.accessoryType = UITableViewCellAccessoryNone;
[selectedRows removeObject:indexPath];
} else {
cell.accessoryType = UITableViewCellAccessoryCheckmark;
[selectedRows addObject:indexPath];
}
}
In your cellForRow... method you do something similar:
BOOL selected = [selectedRows containsObject:indexPath];
cell.accessoryType = selected ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;
Just use
NSMutableArray *dynamicArray = [NSMutableArray array];
You can add and delete objects from this at will. Just be sure to use the NSNumber wrapper to add primitives:
[dynamicArray addObject:[NSNumber numberWithInt:indexNumber]];
// or
[dynamicArray addObject:#(indexNumber)];
Instead of an array you can use a index set.
#property (nonatomic,strong) NSMutableIndexSet *pickedIndexPaths;
- (void)viewDidLoad
{
_pickedSIndexPaths = [[NSMutableIndexSet alloc] init];
[super viewDidLoad];
}
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//…
if(indexPath.section == 0) {
cell.textLabel.text = self.sports[indexPath.row][#"sport"][#"name"];
if ([_pickedIndexPaths containsIndex:indexPath.row]) {
[cell setAccessoryType:UITableViewCellAccessoryCheckmark];
} else {
[cell setAccessoryType:UITableViewCellAccessoryNone];
}
}
return cell;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([_pickedIndexPaths containsIndex:indexPath.row]) {
[_pickedIndexPaths removeIndex:indexPath.row];
} else {
[_pickedIndexPaths addIndex:indexPath.row];
}
[tableView reloadData];
}
}
When what you need is a variable length array of boolean values, you can use CFBitVectorRef. This will consume much less memory than using a Cocoa collection designed for objc object values (provided of course that array has many elements) because it consumes 1 bit for each value, rather than a full pointer which points to an individual dynamically allocated reference counted object.
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];