Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

SimonBS

macrumors regular
Original poster
Dec 30, 2009
202
0
Hi,

I have a UITableView in which I have added sections. When using didSelectRowAtIndex with indexPath.row I am not getting the correct value. Let's say I'm in section 2, then it starts the row index (indexPath.row) at 0 again and therefore I am getting the wrong index.

Can anyone tell me how to fix this? I realize that it is possible to get the section index by indexPath.section but I can't figure out how to use this.

Code:
bandDetailViewController.band = [self.programArray objectAtIndex:indexPath.row];
I hope you can help me. Thanks in advance :)
 
i am having the exact same problem but instead of different sections the index starts over after you begin to type and the search/table gets narrowed down to 1 item ... always loads item in 0 position in the array :(

not to hijack your thread but here is mine:
Code:
- (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope
{
	/*
	 Update the filtered array based on the search text and scope.
	 */
	
	[self.filteredListContent removeAllObjects]; // First clear the filtered array.
	
	/*
	 Search the main list for Physicians whose type matches the scope (if selected) and whose name matches searchText; add items that match to the filtered array.
	 */
	for (Physician *physician in listContent)
	{
		if ([scope isEqualToString:@"All"] || [physician.type isEqualToString:scope])
		{
			NSComparisonResult result = [physician.name compare:searchText options:(NSCaseInsensitiveSearch|NSDiacriticInsensitiveSearch) range:NSMakeRange(0, [searchText length])];
            if (result == NSOrderedSame)
			{
				//here is the issue, i want something like addObject:(correct index value)
				[self.filteredListContent addObject:physician];
            }
		}
	}
}
 
@Simon, that's how sectioned tables work. Each section has an index and each row in a section has an index that starts from zero. Normally to present data in a sectioned table you need to have a data model that is also hierarchical.

@D, normally filtered results are displayed in a table with no sections, even if the filtered table has sections. If you want to display the filtered results in a sectioned table then you need to build a hierarchical data structure (or else you'll go mad).
 
@PhoneyDeveloper, i think you might have misunderstood.

basically the first array let say has 4 names: (0)bob, (1)jim, (2)david, & (3)carl. when you select anyone one it loads their correct detailview based on the index (0,1,2,3). however, when you go to search and type d it narrows the table down to just (0)david which is now in row 0. so when you select david it loads bob from the original array.

someone told me to either retain the original value of indexPath.row (prior to the search), or pass it to a new index variable ... no idea how to do that lol
 
@PhoneyDeveloper I see. So the dictionaries (which consists of band names, 'name', and a day of the week, 'weekDay') should be loaded into an array with the section titles?

So if I want to sort my bands after which day in a week they play (oh, this code is for a small festival application), I will load my array with the days (Wednesday - Saturday) and sort all my dictionaries under the days in the array after which day the band will play, right?

If that is so, may I ask you to give a simple code example om this? I Can imagine how it will work but honestly not how it should be written.
I'm new to softwareprogramming (Obj-C in particular) but have a basic understandment of programming after eight years of web development.
 
OK, your filteredListContent list holds the same objects as your listContent array. It just doesn't have all of them. In your didSelectRowAtIndex path you need to check which table the user is clicking in and use the correct data list. In pseudo code

Code:
if (showingFilteredList)
  physicianToShow = [filteredList objectAtIndex:indexPath.row];
else // if showing the main list
 physicianToShow = [listContent objectAtIndex:indexPath.row];

// push the detail view here and tell it to show the physicianToShow
 
@PhoneyDeveloper, i think thats what i am doing unless i missed something

Code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    UIViewController *detailsViewController = [[UIViewController alloc] init];
	/*
	 If the requesting table view is the search display controller's table view, configure the next view controller using the filtered content, otherwise use the main list.
	 */
	Physician *physician = nil;
	if (tableView == self.searchDisplayController.searchResultsTableView)
	{
        physician = [self.filteredListContent objectAtIndex:indexPath.row];
    }
	else
	{
        physician = [self.listContent objectAtIndex:indexPath.row];
    }
	detailsViewController.title = physician.name;
	
	NSString* physicianName = [tmpImages objectAtIndex:indexPath.row];
	UIImage* physicianImage = [UIImage imageNamed:physicianName];
	UIImageView* physicianImageView = [[UIImageView alloc] initWithImage:physicianImage];
	physicianImageView.frame = self.view.bounds;
	[detailsViewController.view addSubview:physicianImageView];
	[physicianImageView release];
	
	
    [[self navigationController] pushViewController:detailsViewController animated:YES];
    [detailsViewController release];
}
 
This line probably isn't right

Code:
NSString* physicianName = [tmpImages objectAtIndex:indexPath.row];

Anyway, the code you show is a bad idea. Building an image view and adding it to another view controller's view is a terrible idea.

What you should do is add an @property for a Physician* to your detail view controller. Set that when you create the detailViewController. Then load whatever info needs to be loaded from the Physician object in the view controller's viewDidLoad method for display.
 
PhoneyDeveloper, I imagine that I should use nested arrays for my section-issue. Would you mind giving an example on this?
 
@Simon, the data structure I usually use for a sectioned tableview is an array of arrays of dictionaries. The dictionaries represent each row in the table. The top level array holds the sections. The second level arrays each represent a section. I put the section title into dictionary of the first rows for each section (which I know isn't beautiful but it's easy and it works).

When the view controller loads the code builds this data model from external data, often coming from a database. Note that it isn't required for all the rows of the table to exist at that time. It's possible to load section arrays at a later time when the table asks for them. However your code should know how many rows for each section exist, even if they're not loaded yet.

This is some edited code from one of my tables that works like this. It's called from viewDidLoad. I don't show the code for the various tableView callbacks but most of them are very simple since the data model itself tells how many rows and sections there are and the dictionary for each row has all the info in it to build a row.


Code:
	mSectionsList = [[NSMutableArray alloc] initWithCapacity:10];// top level array

	NSMutableArray*	sectionArray = [[NSMutableArray alloc] init];
	NSDictionary*	rowDictionary;
	NSString*		label = nil;
	NSString*		text = nil;
	NSString*		title = nil;
	
	// Add notes if they exist
	NSArray*	noteStructures = self.noteStructures;
	
	for (MNoteStructure* note in noteStructures)
	{
		label = [self labelForNote];
		text = [note objectForKey:kNoteStructureTextKey];
		rowDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:label, kLabelKey, text, kTextKey, nil];
		[sectionArray addObject:rowDictionary];
		[rowDictionary release];
	}
	
	[mSectionsList addObject:sectionArray];
	[sectionArray release];

	NSArray*		multimedia = self.multimedia;
	sectionArray = [[NSMutableArray alloc] init];
	title = NSLocalizedString(@"Multimedia", @"Title for Multimedia stuff");
	NSInteger		row = 0;
	
	for (MMultimediaRecord* object in multimedia)
	{
		text = [self multimediaTextForRow:row];
		label = [self labelForRow:row];
		if (title)
		{
			rowDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:label, kLabelKey, text, kTextKey, title, kSectionTitleKey,  nil];
			title = nil;
		}
		else
			rowDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:label, kLabelKey, text, kTextKey, nil];

		row++;
		[sectionArray addObject:rowDictionary];
		[rowDictionary release];
	}

	[mSectionsList addObject:sectionArray];
	[sectionArray release];

// More sections added here ...
 
@Simon, the data structure I usually use for a sectioned tableview is an array of arrays of dictionaries. The dictionaries represent each row in the table. The top level array holds the sections. The second level arrays each represent a section. I put the section title into dictionary of the first rows for each section (which I know isn't beautiful but it's easy and it works).

When the view controller loads the code builds this data model from external data, often coming from a database. Note that it isn't required for all the rows of the table to exist at that time. It's possible to load section arrays at a later time when the table asks for them. However your code should know how many rows for each section exist, even if they're not loaded yet.

This is some edited code from one of my tables that works like this. It's called from viewDidLoad. I don't show the code for the various tableView callbacks but most of them are very simple since the data model itself tells how many rows and sections there are and the dictionary for each row has all the info in it to build a row.


Code:
	mSectionsList = [[NSMutableArray alloc] initWithCapacity:10];// top level array

	NSMutableArray*	sectionArray = [[NSMutableArray alloc] init];
	NSDictionary*	rowDictionary;
	NSString*		label = nil;
	NSString*		text = nil;
	NSString*		title = nil;
	
	// Add notes if they exist
	NSArray*	noteStructures = self.noteStructures;
	
	for (MNoteStructure* note in noteStructures)
	{
		label = [self labelForNote];
		text = [note objectForKey:kNoteStructureTextKey];
		rowDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:label, kLabelKey, text, kTextKey, nil];
		[sectionArray addObject:rowDictionary];
		[rowDictionary release];
	}
	
	[mSectionsList addObject:sectionArray];
	[sectionArray release];

	NSArray*		multimedia = self.multimedia;
	sectionArray = [[NSMutableArray alloc] init];
	title = NSLocalizedString(@"Multimedia", @"Title for Multimedia stuff");
	NSInteger		row = 0;
	
	for (MMultimediaRecord* object in multimedia)
	{
		text = [self multimediaTextForRow:row];
		label = [self labelForRow:row];
		if (title)
		{
			rowDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:label, kLabelKey, text, kTextKey, title, kSectionTitleKey,  nil];
			title = nil;
		}
		else
			rowDictionary = [[NSDictionary alloc] initWithObjectsAndKeys:label, kLabelKey, text, kTextKey, nil];

		row++;
		[sectionArray addObject:rowDictionary];
		[rowDictionary release];
	}

	[mSectionsList addObject:sectionArray];
	[sectionArray release];

// More sections added here ...

Thank you very much for your help. I ended up saving my sections as dictionaries in a dictionary. And under every section I stored my rows as dictionaries. You have been a great help!
 
I know that some suggest an array of section dictionaries, where there is an array of row dictionaries held in the section dictionaries. The title of the section also goes into the section dictionaries. This model is slightly more flexible than the array of array of dictionaries that I usually use but also more complex.

Don't know what Simon is actually doing though.
 
The keys in my dictionaries is the section header.

So for example the A-section in my alphabetically sorted list would look like this.

Code:
A =     (
                {
            date = "2011-02-04 20:09:40 +0000";
            description = "Long description.";
            hasDate = 1;
            name = Alphabeat;
            scene = "Store Scene";
            weekDay = Wednesday;
            youtubeVideo = "http://www.youtube.com/watch?v=dB01PTZNpBc";
        },
                {
            date = "2011-02-04 20:09:40 +0000";
            description = "Long description.";
            hasDate = 1;
            name = "Anne Linnet";
            scene = "Store Scene";
            weekDay = Wednesday;
            youtubeVideo = "http://www.youtube.com/watch?v=jWMSqS7fL9k";
        },
                {
            date = "2011-02-04 20:09:40 +0000";
            description = "Long description.";
            hasDate = 1;
            name = Apollo;
            scene = "Store Scene";
            weekDay = Thursday;
            youtubeVideo = "http://www.youtube.com/watch?v=EFo2zQL1gEs";
        },
                {
            date = "2011-02-04 20:09:40 +0000";
            description = "Long description.";
            hasDate = 1;
            name = Aqua;
            scene = "Store Scene";
            weekDay = Thursday;
            youtubeVideo = "http://www.youtube.com/watch?v=5LRb4C-3tJQ";
        }
    );
 
Ah, so each section still has an array (just of dictionaries). Whew! Thought you were suggesting you had no arrays at all. It's certainly possible but, perhaps, just not overly usable.
 
Ah, so each section still has an array (just of dictionaries). Whew! Thought you were suggesting you had no arrays at all. It's certainly possible but, perhaps, just not overly usable.

Oh, yes. I do use an array :)

I played a bit with storing it all in one dictionary or array as I have beensuggested and found this to be a good and easy way. It might not be the best, I'm sure it isn't but it's certainlyneasy to work with.
 
I played a bit with storing it all in one dictionary or array as I have beensuggested and found this to be a good and easy way. It might not be the best, I'm sure it isn't but it's certainlyneasy to work with.
I think in terms of sectioned-table datasource modeling, there is no "best". As is often the case with programming solutions, there are numerous solutions that achieve the same end. :)
 
One of the questions about the data model is: how simple is it to get the data for a specified row? For a non-sectioned table view it's simply:

Code:
NSDictionary* rowDictionary = [dataList objectAtIndex:indexPath.row];
And of course number of rows and number of sections is equally simple.

For a sectioned table view using the "array of arrays of dictionaries" design, to get the rowData is:

Code:
NSArray* sectionArray = [dataList  objectAtIndex:indexPath.section];
NSDictionary* rowDictionary = [sectionArray objectAtIndex:indexPath.row];

and number of sections and number of rows is pretty simple too.

@Simon, How do you get the rowDictionary for your data model?
 
For a sectioned table view using the "array of arrays of dictionaries" design, to get the rowData is:

Code:
NSArray* sectionArray = [dataList  objectAtIndex:indexPath.section];
NSDictionary* rowDictionary = [sectionArray objectAtIndex:indexPath.row];

and number of sections and number of rows is pretty simple too.
Yes, but then you add the wrinkle of section titles and how to store those... ;)
 
@Simon, How do you get the rowDictionary for your data model?

I get it from a plist.

Code:
// load plist
	NSString *path = [[NSBundle mainBundle] pathForResource:@"Bands" ofType:@"plist"];
	NSMutableArray* tmpArray = [[NSMutableArray alloc] initWithContentsOfFile:path];
	
	// sort array after name ascending
	NSSortDescriptor *nameSorter = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES];
	[tmpArray sortUsingDescriptors:[NSArray arrayWithObject:nameSorter]];

Then I run through the tmpArray to create the sections dictionary

Code:
// create the index
    for (int i = 0; i < [tmpArray count]; i++){
		char alphabet = [[[tmpArray objectAtIndex:i] objectForKey:@"name"] characterAtIndex:0];
        NSString *uniChar = [NSString stringWithFormat:@"%C", alphabet];
		
		NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SELF.name beginswith[c] %@", uniChar];
		NSArray *bandsThisAlphabet = [tmpArray filteredArrayUsingPredicate:predicate];
		
		[sections setObject:bandsThisAlphabet forKey:uniChar];
    }
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.