I have a TableView control which I've sectioned/grouped. However this has now broken the code which determines the item I've selected in the Table.
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
/*
When a row is selected, set the detail view controller's detail item to the item associated with the selected row.
*/
//detailViewController.detailItem = [NSString stringWithFormat:#"Row %d", indexPath.row];
if (_delegate != nil) {
Condition *condition = (Condition *) [_conditions objectAtIndex:indexPath.row];
[_delegate conditionSelectionChanged:condition];
}
}
My sample data currently has 6 sections, the first one contains two items with all the rest containing one. If I select the second item in the first section, the details view updates fine, any other selection always shows the first item in the first section (item 0).
I'm assuming this is because indexPath.row is returning 0 for the other sections as it's the first item in that section, which is then causing the first object from my array to be chosen.
How do I fix my code to ensure the correct item is selected? Everything else works fine other than this.
cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"CellIdentifier";
// Dequeue or create a cell of the appropriate type.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryNone;
}
//get the letter in the current section
NSString *alphabet = [conditionIndex objectAtIndex:[indexPath section]];
//get all conditions beginning with the letter
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name beginswith[c] %#", alphabet];
NSArray *conditionsGrouped = [_conditions filteredArrayUsingPredicate:predicate];
if ([conditionsGrouped count] > 0) {
Condition *condition = [conditionsGrouped objectAtIndex:indexPath.row];
cell.textLabel.text = condition.name;
}
return cell;
}
You should take into account indexPath.section, as well.
You should create a bi-dimensional _conditions array, indexed by section and then by row.
Got it working by following the answer at this post:
UITableView Problem with Sections
Related
Sorry if this has been discussed already, I couldn't find what I was after..
I have a .plist file which has 2 arrays in it, which are split exactly how I would like my sections split in my table view.
I have no problem getting an array into a table view, but I cant get my head around how to tell the app that I want one array in the first section and the second array in the second section.
My code at the moment to get an array into a table is this (and it works fine):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//Create string with reference to the prototype cell created in storyboard
static NSString *CellIdentifier = #"PlayerNameCell";
//Create table view cell using the correct cell type
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//Create a dictionary containing the player names
NSDictionary *players = (NSDictionary*) [[self Squad] objectAtIndex:[indexPath row]];
//Set the cell text to the player name
[[cell textLabel] setText:(NSString*)[players valueForKey:#"PlayerFullName"]];
//Set the cell detail text to the squad number
[[cell detailTextLabel] setText:(NSString*)[players valueForKey:#"PlayerSquadNumber"]];
return cell;
}
But now I have another table view where I'll need 2 sections, each reading from different arrays.
Any help would be greatly appreciated.
Many thanks
Just do the following:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView
{
return 2;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"PlayerNameCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if( cell == nil ){
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
if( indexPath.section == 0 ){
NSDictionary *players = (NSDictionary*) [self.array1 objectAtIndex:indexPath.row];
cell.textLabel.text = (NSString*)[players valueForKey:#"PlayerFullName"];
cell.detailTextLabel.text = (NSString*)[players valueForKey:#"PlayerSquadNumber"];
} else {
NSDictionary *players = (NSDictionary*) [self.array2 objectAtIndex:indexPath.row];
cell.textLabel.text = (NSString*)[players valueForKey:#"PlayerFullName"];
cell.detailTextLabel.text = (NSString*)[players valueForKey:#"PlayerSquadNumber"];
}
return cell;
}
Set the number of sections to 2. For each section get the different values with [self.array2 objectAtIndex:indexPath.row].
I don't know how you are saving the plist into 2 arrays, but if you need help with that let me know.
Okay, so you have 2 arrays at the top level, and each of those arrays contains arrays, right?
You've got a couple methods you need to adjust. Return the number of top level arrays for the number of sections in the table view. In cellForRowAtIndexPath:, return the correct object for the correct section/row. Something like
[[sectionsArray objectAtIndex:indexPath.section] objectAtIndex:indexPath.row]
Setup: I have a property called "_itemListArray(ivar)", that is set to a list of "Item(NSString itemName, NSString itemPrice)". I populate a UITableView with these items and the user can select multiple rows, displaying a checkmark on that row. The indexPath of the checked cell is stored to an IVAR(_selectedItemRows). If the user selects the row again, the checkmark accessory is set to none and the indexPath is removed from the IVAR(_selectedItemRows). In "cellForRowAtIndexPath" I check the currently queued indexPath against all indexPaths in _selectedItemRows(array of indexPaths of checked cells). If the index path is in array, I check the dequeued cell, if not, I uncheck it.
Problem: The checkmark accessory is set correctly(didSelectRowAtIndexPath), but when I scroll, it acts funky. For example, if I check the first cell then scroll down, then scroll back up to the first cell, nslogs have verified that my program knows to check the cell, but it doesn't seem to.
Also, if I check 2 or more cells, scroll down, then scroll back up, usually the last cell is the only checked.
Code:
#implementation
#synthesize itemListArray = _itemListArray;
#synthesize selectedItemRows = _selectedItemRows;
-(void)setItemListArray:(NSArray *)itemListArray
{
_itemListArray = itemListArray;
[_propTableView reloadData];
}
- (void)viewDidLoad
{
[super viewDidLoad];
_selectedItemRows = [[NSMutableArray alloc] init];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [_itemListArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Item Selected Reuse"; //Identifier of prototype cell
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (nil == cell) { //If somethong goes wrong, all hell breaks loose.
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
NSLog(#"%s", __PRETTY_FUNCTION__);
}
// Configure the cell...
Item *curItem = [_itemListArray objectAtIndex:indexPath.row]; //Get the model information at row location.
cell.textLabel.text = curItem.itemName; //Set the name of the item in title field
cell.detailTextLabel.text = curItem.itemPrice; //Set the price of the item in the detail field.
for(NSIndexPath * elem in _selectedItemRows)
{ //Enumerate through checked cells
//NSIndexPath *ip = [_selectedItemRows objectAtIndex:x];
if ([indexPath compare:elem] == NSOrderedSame) { //If the current cell index path ='s any index path in the array of checked cells, check this cell.
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
return cell;
}
//pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; //Get cell clicked on.
if(cell.accessoryType == UITableViewCellAccessoryNone){ //When selected, if the cell is checked, uncheck it.
cell.accessoryType = UITableViewCellAccessoryCheckmark;
[_selectedItemRows addObject:indexPath]; //Add the index path of checked cell into array to use later for comparisons
} else {
if(cell.accessoryType == UITableViewCellAccessoryCheckmark){ //If the cell is checked, uncheck it when clicked on
cell.accessoryType = UITableViewCellAccessoryNone;
[_selectedItemRows removeObject:indexPath]; //Remove that index path of unchecked cell from index array
}
}
[tableView deselectRowAtIndexPath:indexPath animated:YES];//Deselect row after done.
}
#end
//Other code left out for brevity sake
You have a logic error in your code. Think about what happens in this bit of code:
for(NSIndexPath * elem in _selectedItemRows)
{ //Enumerate through checked cells
//NSIndexPath *ip = [_selectedItemRows objectAtIndex:x];
if ([indexPath compare:elem] == NSOrderedSame) { //If the current cell index path ='s any index path in the array of checked cells, check this cell.
cell.accessoryType = UITableViewCellAccessoryCheckmark;
} else {
cell.accessoryType = UITableViewCellAccessoryNone;
}
}
Unless the index path for the current row happens to be the last one in _selectedItemRows the cell will have the checkmark cleared. It will set the check mark when it finds it in _selectedItemRows and then unset it as it continues searching. Instead you want to replace this with something like the following:
cell.accessoryType = UITableViewCellAccessoryNone;
for(NSIndexPath * elem in _selectedItemRows)
{ //Enumerate through checked cells
//NSIndexPath *ip = [_selectedItemRows objectAtIndex:x];
if ([indexPath compare:elem] == NSOrderedSame) { //If the current cell index path ='s any index path in the array of checked cells, check this cell.
cell.accessoryType = UITableViewCellAccessoryCheckmark;
break;
}
}
Recently started developing apps, so excuse my ignorance. I have a tableView, and when a cell in the table view is clicked, I want to insert a new row directly below it. This currently works in my code. However, I also want the row that has been inserted to be removed once the cell has been clicked again. This is giving me the NSRangeException saying I am out of bounds in my array.
I figured this probably has to do with my tableView delegate/data methods, so I set up break points at each of them. With the break points enabled, the cell is removed perfectly. However, when I disable the breakpoints, and let the application run on its own, it crashes. How could break points possibly be affecting this?
Here is the relevant code:
- (NSInteger) numberOfSectionsInTableView:(UITableView *)songTableView{
return 1;
}
- (NSInteger)tableView:(UITableView *)songTableView
numberOfRowsInSection:(NSInteger)section{
bool debug = false;
if (debug) NSLog(#"--TableView: rankings");
if (expandedRow == -1)
return [self.songs count];
else //one row is expanded, so there is +1
return ([self.songs count]+1);
}
- (UITableViewCell *)tableView:(UITableView *)songTableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath{
bool debug = false;
if (debug) NSLog(#"--tableView: tableView");
NSUInteger row = [indexPath row];
if (row == expandedRow){ //the expanded row, return the custom cell
UITableViewCell *temp = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"test"];
return temp;
}
UITableViewCell *cell = [tableViewCells objectAtIndex:row];
return cell;
}
}
- (NSString *)tableView:(UITableView *)songTableView
titleForHeaderInSection:(NSInteger)section{
//todo: call refresh title
return #"The Fresh List";
}
- (CGFloat)tableView:(UITableView *)songTableView
heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 44.0; //same as SongCell.xib
}
- (void)tableView: (UITableView *)songTableView
didSelectRowAtIndexPath: (NSIndexPath *)indexPath {
bool debug = true;
//todo: if the user selects expanded cell, doesn't do anything
SongCell *cell = (SongCell *)[songTableView cellForRowAtIndexPath:indexPath];
if (cell->expanded == NO){
//change cell image
cell.bgImage.image = [UIImage imageNamed:#"tablecellbg_click.png"];
cell->expanded = YES;
//add new cell below
NSInteger atRow = [indexPath row] + 1;
NSIndexPath *insertAt = [NSIndexPath indexPathForRow:atRow inSection:0];
NSArray *rowArray = [[NSArray alloc] initWithObjects:insertAt, nil];
if (debug) NSLog(#"Expanded row: %d", atRow);
expandedRow = atRow;
[tableView insertRowsAtIndexPaths:rowArray withRowAnimation:UITableViewRowAnimationTop];
}else { //cell is already open, so close it
//change cell image
cell.bgImage.image = [UIImage imageNamed:#"tablecellbg.png"];
cell->expanded = NO;
NSIndexPath *removeAt = [NSIndexPath indexPathForRow:expandedRow inSection:0];
NSArray *rowArray = [[NSArray alloc] initWithObjects:removeAt, nil];
if(debug) NSLog(#"--about to delete row: %d", expandedRow);
expandedRow = -1;
[tableView deleteRowsAtIndexPaths:rowArray withRowAnimation:UITableViewRowAnimationTop];
//remove expaned cell below
}
}
This is just a guess, but it's a good idea to wrap code that changes the table structure in calls to
[tableView beginUpdates];
[tableView endUpdates];
I bet this returns null: [NSIndexPath indexPathForRow:expandedRow inSection:0]; and if it does it blows ...
hth
I hate to answer my own questions but I figured it out.
I was loading up my tableView from an array of objects. When I added the cell, that array still only held 30 objects, whereas my table held 31. I was correctly returning the numberOfRowsInSection method.
Here is the modification I made. Notice the extra else if:
NSUInteger row = [indexPath row];
if (row == expandedRow){ //the expanded row, return the custom cell
if(debug) NSLog(#"row == expandedRow");
UITableViewCell *temp = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"test"];
return temp;
}
else if (expandedRow != -1 && row > expandedRow)
row--;
My array of objects and the UITableViewCells were suppose to match up 1-1. After the expanded row, indexPath's row because off by 1. Here is my quick fix to this problem, although I'm sure there is a better way to solve this.
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!
I am wondering, I have an empty table and I want to display a text in that table, then I want to clear that text when the table has been inserted with something else. And the text will appear again when I completely remove everything in the table again.?
In your dataSource for your UITableView there is a method:
-tableView:objectValueForTableColumn:row:
In that method you can check your NSArray or whatever you have supplying data to your table to see if there are any entries. If there are none and the request is for row zero, column zero, just return your 'no data' string.
To clarify syntax, assume you have an NSArray named songs which you wish to print:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//boilerplate cell production
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
//check if we have data to supply, if not set message to "No Songs Available"
//otherwise print the name of the song in each cell
if (indexPath.row == 0 && indexPath.section == 0 && [songs count] == 0)
cell.textLabel.text = #"No Songs Available";
else if (indexPath.row < [songs count])
cell.textLabel.text = [(Song *)[songs objectAtIndex:indexPath.row] songName];
return cell;
}