Difficulties adding an index to a table view

Discussion in 'iOS Programming' started by Locker, Jun 8, 2009.

  1. Locker macrumors 6502

    Joined:
    Aug 22, 2007
    Location:
    Staffordshire, UK
    #1
    I'm trying to add an index to an alphabetically sorted table view. At the moment, I have the index appearing and working correctly (it jumps to the correct section). However, each section has every cell in it, rather than just cells which start with the sections letter.

    I'm a bit confused as to what I should return for numberOfRowsInSection - as it's obviously going to be different for each section, and subject to change so needs to be worked out dynamically.

    My cellForRowAtIndexPath currently looks like this:

    Code:
    My_Class *listItem = [listItemArray objectAtIndex:[indexPath indexAtPosition:1]];
    	NSString *CellIdentifier = [NSString stringWithFormat:@"%d", listItem.listItemID];
    	
        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
        if (cell == nil) {
            cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
    		cell.text = listItem.name;
    	}
        
        // Set up the cell...
    
        return cell;
    I know that this too will obviously have to change, but I'm really not sure how. If anyone could help me out and point me in the right direction it would be much appreciated :)
     
  2. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #2
    How are you storing the list of items that your table is using? You may need to consider breaking this into "section-aware" arrays, via an array of arrays or perhaps a dictionary.

    In numberOfRowsInSection:, you are going to want to put in some logic to return the number of rows based on the section that is passed as a parameter. Then in cellForRowAtIndexPath, you are going to need similar logic that gets the cell data based on which section and row have been passed (both of these values are part of the indexPath parameter).

    Hope that helps get you started.
     
  3. Locker thread starter macrumors 6502

    Joined:
    Aug 22, 2007
    Location:
    Staffordshire, UK
    #3
    They're stored in an NSMutableArray, which is populated from an NSDictionary. Like this:

    Code:
    listItems = [[NSMutableArray array] retain];
    	for (NSDictionary *singleItem in [listDict objectForKey:@"county"]) {
    		My_Class *listItem = [[[My_Class alloc] init] autorelease];
    		listItem.listItemID = [(NSNumber *)[singleItem objectForKey:@"id"] intValue];
    		listItem.name = [singleItem objectForKey:@"name"];
    		[listItems addObject:listItem];
    	}
    I'm new to Objective-C development so if anyone could give some examples of how a solution could work I'd very much appreciate it.
     
  4. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #4
  5. Locker thread starter macrumors 6502

    Joined:
    Aug 22, 2007
    Location:
    Staffordshire, UK
    #5
    I get how table views work, it's the manipulation to populate them that I'm struggling with. I've now added a property to "listItem" called letter, with the char data type. It stores the first letter of that item.

    I've checked and that's all working fine, so I expected the following code to output all names beginning with A:

    Code:
    for (int count = 0; [[listItem.name characterAtIndex:0] charValue] == @"A"; count++) {
    			NSLog(@"%@", listItem.name);
    		}
    But, it didn't. I get three warnings:

    - Invalid receiver type 'unichar'.
    - cast to pointer from integer of different size.
    - comparison between pointer and integer.

    What am I doing wrong now?
     
  6. jnic macrumors 6502a

    Joined:
    Oct 24, 2008
    Location:
    Cambridge
  7. Locker thread starter macrumors 6502

    Joined:
    Aug 22, 2007
    Location:
    Staffordshire, UK
    #7
    So how would I present it as a char?
     
  8. Locker thread starter macrumors 6502

    Joined:
    Aug 22, 2007
    Location:
    Staffordshire, UK
    #9
    Well, I've tried this:

    Code:
    char testChar = 'A';
    for (int count = 0; [[listItem.name characterAtIndex:0] charValue] == testChar; count++) {
    			NSLog(@"%@", listItem.name);
    		}
    However, I still get the errors:

    - invalid receiver type 'unichar'.
    - cast to pointer from integer of different size
     
  9. jnic macrumors 6502a

    Joined:
    Oct 24, 2008
    Location:
    Cambridge
    #10
    On which lines of that code do those errors occur?

    Currently, your for loop can never output more than one name before it exits.
     
  10. Locker thread starter macrumors 6502

    Joined:
    Aug 22, 2007
    Location:
    Staffordshire, UK
    #11
    Very true, I've made myself look quite stupid posting that. Anyway, here's how things are looking at the moment:

    [​IMG]
     
  11. jnic macrumors 6502a

    Joined:
    Oct 24, 2008
    Location:
    Cambridge
    #12
    Well, characterAtIndex returns a unichar, which is a primitive type (it's a typedef'd short), and you're then trying to call charValue on it, despite the fact that it isn't an NSNumber.
     
  12. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #13
    A few comments:

    Don't use characterAtIndex for anything.

    Don't use == to compare strings.

    Read the "String programming guide for core foundation" before you go any further.

    If you want to compare substrings then do that. Make a substring from your longer string and compare it, as a string, to your example string, like @"A".
     
  13. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #14
    You may get how table views work but based on your questions in this thread, you don't really get how sectioned or grouped tables work yet. That's why I linked to a couple of tutorials that deal with those specifically. I can probably also find you a sample app from Apple that shows how this is done, if you're interested.

    If your list of items is anything but really short, looping through all of them just to find the ones for a specific section seems inefficient. I believe you're better off breaking your list into multiple lists, one for each section, populating those lists and then having the datasource methods pull data from those lists.

    In my app, [app]CraigsHarvest[/app], my search results tableView has three sections: Today, Yesterday, and Older. Therefore, I set up three NSMutableArrays, one for each section. Then when retrieving the search results, I add to each of these arrays based on the dates of the postings. Then in numberOfRowsInSection: I do something like:
    Code:
    if (section == 0)
        return [todayArray count];
    else if (section == 1)
        return [yesterdayArray count];
    else
        return [olderArray count];
    
    (If I had more sections, I'd probably convert this to a switch block.)

    and then in cellForRowAtIndexPath: I do something similar:
    Code:
    if (section == 0)
        cell.text = [todayArray objectAtIndex:indexPath.row];
    else if (section == 1)
        cell.text = [yesterdayArray objectAtIndex:indexPath.row];
    else
        cell.text = [olderArray objectAtIndex:indexPath.row];
    
     
  14. Locker thread starter macrumors 6502

    Joined:
    Aug 22, 2007
    Location:
    Staffordshire, UK
    #15
    Aha, again that's my bad. I didn't realize that charValue was an NSNumber method, I guess it teaches me for "guess typing" and seeing what Xcode presents to me as suggestions.

    If you don't use characterAtIndex for anything, what's it there for? And I guess you use the isEqualToString method of NSString then?

    That sounds logical, though my sections are simply letters of the alphabet and whilst I could probably get the above method working - surely it would be tedious to create a long switch block covering each letter? Is there anyway of achieving it a little more dynamically?

    Thanks for all of your help guys. My previous programing experience consists primarily of PHP, as you can probably tell :eek:
     
  15. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #16
    As I alluded to in an earlier post, you could have an array of arrays or a dictionary to handle this. In the former, each object of the top array is an array that contains the items for a particular starting-letter. So, a 26-element array. In the latter, each dictionary item would be a key that's the starting-letter and a value that's the array of items for that letter.
     
  16. Buschmaster macrumors 65816

    Buschmaster

    Joined:
    Feb 12, 2006
    Location:
    Minnesota
    #17
    The more dynamic way would be to have an array of all the letters.

    Then...

    Code:
    return [myLettersArray objectAtIndex:indexPath.section];
    That's untested, but let me know if you need any more help. :)
     
  17. Locker thread starter macrumors 6502

    Joined:
    Aug 22, 2007
    Location:
    Staffordshire, UK
    #18
    Yes, I already use the following to define the section titles:

    Code:
    - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    	return [alphabetArray objectAtIndex:section];
    }
    However, I'm thinking of dynamically populating the lists in a better way than explicitly saying "If the section is A, load with an array called dataLetterA, if the section is B, load with an array called dataLetterB" or what have you. I'm perhaps getting a little ahead of myself, as I don't have anything working at all yet.

    So, I already have my alphabetArray which holds all of the letters in the alphabet. How would I add arrays to this array as dejo suggested? My initial thoughts to test it out were:

    Code:
    NSMutableArray *dataLetterA = [[NSMutableArray alloc] init];
    	[dataLetterA addObject:@"Amazing Item"];
    	[dataLetterA addObject:@"Awesome Item"];
    	[[alphabetArray objectAtIndex:0] addObjectsFromArray:dataLetterA];
    This seems to crash the simulator though, am I remotely close?
     
  18. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #19
    Code:
    NSMutableArray *alphabetArray = [[NSMutableArray alloc] init];
    NSMutableArray *dataLetterA = [[NSMutableArray alloc] init];
    [dataLetterA addObject:@"Amazing Item"];
    [dataLetterA addObject:@"Awesome Item"];
    [alphabetArray addObject:dataLetterA];
    [dataLetterA release];
    NSMutableArray *dataLetterB = [[NSMutableArray alloc] init];
    [dataLetterB addObject:@"Beautiful Item"];
    [dataLetterB addObject:@"Boring Item"];
    [alphabetArray addObject:dataLetterB];
    [dataLetterB release];
    ...
    
    Continue for all the letters in the alphabet. Now you have an array (alphabetArray) where objectAtIndex:0 corresponds to the (sub)array of items for the letter A. This is a rather hard-coded approach and could readily be adapted to build alphabetArray dynamically based on the data from a pre-existing list.
     
  19. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #20
    It's there for people who really understand Unicode. That's not you and it's not me. I never use that method.

    You have to guess? NSStrings are only superficially like C-style string arrays. You need to think of them as objects. You don't need to understand how they work inside. You just need to learn their apis. Yes, use isEqualToString to compare them. You can also use the more complex compare methods described in the docs if you need more control of the comparison.

    BTW, whoever wrote those compare methods needed to use characterAtIndex.
     
  20. Locker thread starter macrumors 6502

    Joined:
    Aug 22, 2007
    Location:
    Staffordshire, UK
    #21
    Thanks for that, I'm out at the moment but will try it when I get home. If I can get the static version working that'll be a start :)

    Fair enough :)

    Thanks for that, makes sense :). Also, I didn't have to guess, I'm sure you know what I meant :p
     
  21. Locker thread starter macrumors 6502

    Joined:
    Aug 22, 2007
    Location:
    Staffordshire, UK
    #22
    Hmmm, nothing seems to be going right for me. I'm trying dejo's idea, and started with the below code:

    Code:
    listItems = [[NSMutableArray array] retain];
    	for (NSDictionary *singleItem in[listDict objectForKey:@"county"]) {
    		My_Class *listItem = [[[My_Class alloc] init] autorelease];
    		listItem.listItemID = [(NSNumber *)[singleItem objectForKey:@"id"] intValue];
    		listItem.name = [singleItem objectForKey:@"name"];
    		[listItems addObject:listItem];
    if([[county.name substringWithRange:NSMakeRange(0, 1)] isEqualToString:@"A"]) {
    			dataLetterA = [[NSMutableArray alloc] init];
    			[dataLetterA addObject:listItem.name];
    			[alphabetArray addObject:dataLetterA];
    			[dataLetterA release];
    		}
    	}
    
    However, this shows the following in the console:

    Code:
    2009-06-10 20:16:19.935 B and B Seeker[24215:20b] *** -[NSCFArray length]: unrecognized selector sent to instance 0x569450
    2009-06-10 20:16:19.937 B and B Seeker[24215:20b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSCFArray length]: unrecognized selector sent to instance 0x569450'
    2009-06-10 20:16:19.937 B and B Seeker[24215:20b]
    Once again, I can't see why :(
     
  22. PhoneyDeveloper macrumors 68030

    PhoneyDeveloper

    Joined:
    Sep 2, 2008
    #23
    length is an NSString method, and also some other classes have that method. However, it's being called on an NSArray, which doesn't have that method. This causes the runtime exception that you see: unrecognized selector sent to instance.

    One common cause of this exception is not properly retaining an object that is used at a later time. What happens is that you have an ivar. You set the value of that ivar to an object that is autoreleased. Then at a later time the memory that was used by the object is reused for another object of another kind. Then when your code goes to use the ivar there's something else there. When I say ivar it could also be an autoreleased object added to an array where the array is the ivar.

    I don't see the bug in the code that you showed. You might be calling length or some framework code might call it. For instance isEqualToString most likely calls length on the strings that it's comparing.

    One other thing. It helps to have a breakpoint set on objc_exception_throw so the debugger is more helpful with where the runtime exception is thrown. You can set one up like this:

    http://coderslike.us/tag/exception/
     
  23. dejo Moderator

    dejo

    Staff Member

    Joined:
    Sep 2, 2004
    Location:
    The Centennial State
    #24
    It's gonna be pretty difficult for us to debug this without seeing some more code. For example, how is My_Class defined? And where is county defined and populated? Also, not sure where / how listItems and listDict are declared. Etc.

    Have you tried setting a breakpoint and stepping through the code to see where it's blowing up?
     
  24. Locker thread starter macrumors 6502

    Joined:
    Aug 22, 2007
    Location:
    Staffordshire, UK
    #25
    Thanks for all of your help guys, I'm nearly there.

    The only remaining problem that I seem to have is keeping my cell identifier unique. This used to be sufficient:

    Code:
    My_Class *listItem = [listItems objectAtIndex:indexPath.row];
    NSString *CellIdentifier = [NSString stringWithFormat:@"%d", listItem.listItemID];
    The problem is that now there are multiple sections, the indexPath.row keeps starting over, thus returning duplicate identifiers and messing things up. Any ideas?
     

Share This Page