1

I need to sort by value instead of Key, I think....

Heres where I populate my arrarys

const char *sql = "select cid, category from Categories ORDER BY category DESC";
sqlite3_stmt *statementTMP;

int error_code = sqlite3_prepare_v2(database, sql, -1, &statementTMP, NULL);
if(error_code == SQLITE_OK) {
    while(sqlite3_step(statementTMP) == SQLITE_ROW)
    {
        int cid = sqlite3_column_int(statementTMP, 0);
        NSString *category = [[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statementTMP, 1)];

        NSArray *arr=[[NSArray alloc]initWithObjects:category,nil];

        [arrayTmp setObject:arr forKey:[NSString stringWithFormat:@"%i",cid]];
        [self.cidList addObject:[NSString stringWithFormat:@"%i",cid]];

        [category release];
        [arr release];
    }
}
sqlite3_finalize(statementTMP);
sqlite3_close(database);

self.allCategories = arrayTmp;
[arrayTmp release];

Heres the method where the arrays are re-sorted.

- (void)resetSearch {

NSMutableDictionary *allCategoriesCopy = [self.allCategories mutableDeepCopy];
self.Categories = allCategoriesCopy;
[allCategoriesCopy release];
NSMutableArray *keyArray = [[NSMutableArray alloc] init];
[keyArray addObject:UITableViewIndexSearch];
[keyArray addObjectsFromArray:[[self.allCategories allKeys] 
                               sortedArrayUsingSelector:@selector(compare:)]];
self.keys = keyArray;
[keyArray release];
}

This is a problem i've had for some time, last time I looked at this I could find an altervative to sortedArrayUsingSelector compare?

EDIT

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

NSUInteger section = [indexPath section];
NSUInteger row = [indexPath row];

NSString *key = [keys objectAtIndex:section];
NSArray *nameSection = [Categories objectForKey:key];

static NSString *SectionsTableIdentifier = @"SectionsTableIdentifier";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:
                         SectionsTableIdentifier ];
if (cell == nil) {
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 
                                   reuseIdentifier: SectionsTableIdentifier ] autorelease];
}

cell.textLabel.text = [nameSection objectAtIndex:row];
return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { 

NSUInteger section = [indexPath section];
NSUInteger row = [indexPath row];

NSString *key = [keys objectAtIndex:section];
NSArray *nameSection = [Categories objectForKey:key];

NSLog(@"the selected cid is = %i",[key intValue]); 

selectButton.enabled = YES;
}

Anyone?

Jules
  • 7,190
  • 14
  • 93
  • 171
  • 2
    Only masochists use the SQLite C API directly in Objective-C. [Use FMDB](http://github.com/ccgus/fmdb) (a SQLite wrapper) or [CoreData](http://developer.apple.com/library/mac/#documentation/cocoa/conceptual/CoreData/cdProgrammingGuide.html) (an object graph manager) instead. – Dave DeLong May 30 '11 at 20:15
  • @Dave, it works for me, I had problems with FMDB, never tried CoreData – Jules May 30 '11 at 20:24
  • 1
    @Jules -- You might want to detail exactly what your problem is. What behavior do you expect and what exact behavior are you seeing. It's hard to infer from the code samples exactly what the problem is unless there is a glaring error. – TechZen Jun 03 '11 at 03:19
  • @TechZen With the opening code I have, there's a tableview, a searchbar and a keypad always on screen. On loading the tableview is populated with all the categories from the database. Although these are added to the main array in alphabetical order they down show in the tableview as such. This is because resetsearch is called, which changes what's shown. As the user types into the searchbar the results a limited, this is done by function handleSearchForTerm which removes items from the array used to show the results. With the resetSearch code you provided I get no data shown in the tableview. – Jules Jun 03 '11 at 06:22
  • Cont... I want to see result in the tableview in alphabetical order even when the user inputs / limits the results by typing in the searchbar. – Jules Jun 03 '11 at 06:23
  • If I understood correctly then what you wish to do to get categories from database & display it on a tableView with alphabetical sorting, index on right & search bar on top. Ideally, you would like to display the Contacts application kind of a view. Is this correct? – Sagar Jun 06 '11 at 05:38
  • @Sagar, well I don't need section or the alphabetical bar like the contacts app, but apart from that yes. – Jules Jun 07 '11 at 08:30

6 Answers6

4

Your obviously attempting to construct an array for use in the -[UITableviewDatasource sectionIndexTitlesForTableView:]. As such, you need an array that looks like this (pseudo-code):

[UITableViewIndexSearch, 0_sectionTitle, 1_sectionTitle, 2_sectionTitle, ...]

I think your immediate problem is that you try to add the UITableViewIndexSearch string constant to the array before you sort which makes it impossible for it end up as the first element unless all your other elements sort below U.

The fix is simple, just add the constant after the sort. You can clean the code up while you're at it:

  NSMutableArray *secIdx=[NSMutableArray arrayWithCapacity:[[self.allCategories allKeys] count]];
  [secIdx addObjectsFromArray:[self.allCategories allKeys]];
  [secIdx sortUsingSelector:@selector(compare:)];
  [secIdx insertObject:UITableViewIndexSearch atIndex:0];
  self.keys=secIdx;

Note that secIdx is autoreleased so you don't have to release it.

Aside from this problem, your code has a lot of unnecessary/dangerous elements that will make your app fragile and hard to maintain.

  1. You are using a lot of init for objects that you could use autoreleased convenience methods for. The 'init`s poise the risk of memory leaks but give you no advantage.
  2. You need to wrap scalar values in objects so they can be easily managed in collections.
  3. You are using an unnecessary array.

You can rewrite the first block like so:

const char *sql = "select cid, category from Categories ORDER BY category DESC";
sqlite3_stmt *statementTMP;

int error_code = sqlite3_prepare_v2(database, sql, -1, &statementTMP, NULL);
if(error_code == SQLITE_OK) {
    NSNumber *cidNum; //... move variable declerations outside of loop
    NSString *category; //.. so they are not continously recreated
    [self.allCategories removeAllObjects]; //... clears the mutable dictionary instead of replacing it
    while(sqlite3_step(statementTMP) == SQLITE_ROW){
        cidNum=[NSNumber numberWithInt:(sqlite3_column_int(statementTMP, 0))]; 
        category=[NSString stringWithUTF8String:(char *)sqlite3_column_text(statementTMP, 1)];
        //... adding the autoreleased category and cidNum to array/dictionary automatically retains them
        [self.allCategories addObject:category forKey:cidNum]; 
        [self.cidList addObject:cidNum];

        //[category release]; ... no longer needed
        //[arr release]; ... no longer needed
    }
}
sqlite3_finalize(statementTMP);
sqlite3_close(database);

//self.allCategories = arrayTmp; ... no longer needed
//[arrayTmp release]; ... no longer needed
TechZen
  • 63,819
  • 15
  • 116
  • 144
  • I had inferred that `self.allCategories` was a NSMutableDictionary. If it is just a NSDictionary, the you will need to change the property to a mutable dictionary or use a temp mutable dictionary and then convert to the property. I suggest just using a mutable dictionary to start with unless you expect your list to be thousands of elements long. – TechZen Jun 03 '11 at 13:36
  • I can't tell what is going on from these little snippets of code. I have no idea what variable/property holds what data and why. If you like, you can post a link to the header(.h) and implementation(.m) files and I can take a look at. Alternatively, you can can email them to me (click on my name in the badge for the email.) – TechZen Jun 03 '11 at 15:01
  • @Jules -- I haven't received anything. It's techzen at me dot com. – TechZen Jun 04 '11 at 12:52
  • @Jules -- Got it but see my rely – TechZen Jun 04 '11 at 13:11
3

Use -sortedArrayUsingComparator: (or -sortedArrayUsingFunction:context: if you can't use blocks). Example:

NSDictionary *categories = [self allCategories];
NSArray *keysSortedByValue = [[categories allKeys] sortedArrayUsingComparator:
^(id left, id right) {
    id lval = [categories objectForKey:left];
    id rval = [categories objectForKey:right];
    return [lval compare:rval];
}];
Jeremy W. Sherman
  • 34,925
  • 5
  • 73
  • 108
  • @Jules Please be more precise. In what way does it fail to work? Are there any error messages? If so, what is the full text of each error message? – Jeremy W. Sherman May 30 '11 at 14:26
  • @Jules It's just a mutability issue. You need the equivalent of an Obj-C cast to mutable instance: `[[keysSortedByValue mutableCopy] autorelease]`, or using the variables in your screenshot, `self.keys = [[keyArray mutableCopy] autorelease]`. – Jeremy W. Sherman May 30 '11 at 14:46
  • @Jules Alternatively (and cleaner), `[[self keys] setArray:allKeys]`. This just replaces the keys in your current `keys` array with all the keys from `allKeys`, which means you get to skip deallocating the old `keys` array and copying the new one. Far nicer. – Jeremy W. Sherman May 30 '11 at 14:48
  • @Jules Replace the `-compare:` message with any message send or function call that will compare the two arrays and return an `NSComparisonResult` specifying their ordering. I assumed you had a dictionary mapping scalar key to scalar value and wanted to order the keys by the sort order of the value. Most basic Foundation types respond to `compare:`, so that's what I used in the sample code, but the same idea of using `-sortedArrayUsingComparator:` and working with the values rather than the keys applies. – Jeremy W. Sherman May 30 '11 at 16:35
  • I don't have a compare function, not sure how to proceed **Help** please – Jules May 30 '11 at 19:19
1

You could make a small model class Category and implement compare inside of it, then sort an array of those objects using that compare:.

Here's some info - How to sort an NSMutableArray with custom objects in it?

Community
  • 1
  • 1
shawnwall
  • 4,331
  • 1
  • 24
  • 35
1

Perhaps you're looking for NSSortDescriptor (and the corresponding sort method, -[NSArray sortedArrayUsingDescriptors]) and friends?

Steven Kramer
  • 8,375
  • 2
  • 34
  • 43
0

On your sorting function u should try this:

NSArray *cntxt; //im not sure this is the correct type that ur using on keyArray
[keyArray addObjectsFromArray:[self.allCategories allKeys]];
[keyArray sortUsingFunction:compareFunction context:cntxt];

And the compare function you modify to your needs

NSInteger compareFunction(id x, id y, void *context) {
    //NSArray *ctxt = context;
    NSArray *c1 = x;
    NSArray *c2 = y;

    if ([c1 value] < [c2 value])
        return NSOrderedDescending;
    else if ([c1 value] > [c2 value])
        return NSOrderedAscending;
    else 
        return NSOrderedSame;
}

Edit: After reading your comments and after relooking at your code, it seems like that your keyArray as objects of the type NSString, so you should change:

NSInteger compareFunction(id x, id y, void *context) {
    //NSString *ctxt = context;
    NSString *c1 = x;
    NSString *c2 = y;
    NSComparisonResult result;
    result = [c1 compare:c2];

    if (result<0)
        return NSOrderedAscending;
    else if (result>0)
        return NSOrderedDescending;
    else 
        return NSOrderedSame;
}
iruleonu
  • 849
  • 12
  • 16
  • Thanks @iruleonu, I'm getting a couple of warnings `NSArray may not respond to 'value'`. I really hope this works :) – Jules Jun 07 '11 at 06:54
  • Ive written [c1 value] only as an example. This compare will depend on the type of the objects your NSArray keyArray contains. – iruleonu Jun 07 '11 at 08:46
  • @iruleonu ok this compiles and runs, but doesn't produce a list of values in alphabetical order. Maybe your sorting the foreign key id number ? – Jules Jun 07 '11 at 09:12
  • @iruleonu Yes this seems to be the case https://skitch.com/mindwarpltd/fdmua/bgcatselbuild-categoryseltableviewcontroller.m – Jules Jun 07 '11 at 09:16
  • uhm this should work then, change the line that fills the content of the keyArray with keys to values, [keyArray addObjectsFromArray:[self.allCategories allValues]]; – iruleonu Jun 07 '11 at 15:25
0

If I understood correctly then what you wish to do to get categories from database & display it on a tableView with alphabetical sorting, index on right & search bar on top. Ideally, you would like to display the Contacts application kind of a view. If that's correct, use below code for fetching items from DB & rebuilding (or resetting) it -

const char *sql = "select cid, category from Categories ORDER BY category DESC";
sqlite3_stmt *statementTMP;

NSMutableArray *arrayTmp = [[NSMutableArray alloc] init];

int error_code = sqlite3_prepare_v2(database, sql, -1, &statementTMP, NULL);
if(error_code == SQLITE_OK) {
    while(sqlite3_step(statementTMP) == SQLITE_ROW) {
         int cid = sqlite3_column_int(statementTMP, 0);
         NSString *category = [[NSString alloc] initWithUTF8String:(char *)sqlite3_column_text(statementTMP, 1)];

         NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
         [dict setObject:category forKey:@"Category"];
         [dict setObject:[NSNumber numberWithInt:cid] forKey:@"CID"];

         [arrayTmp addObject:dict];
         [dict release];
         [category release];
    }
}

sqlite3_finalize(statementTMP);
sqlite3_close(database);

self.allCategories = arrayTmp;
[arrayTmp release];

And then rebuild the items using this function -

- (void)rebuildItems {
    NSMutableDictionary *map = [NSMutableDictionary dictionary];

    for (int i = 0; i < allCategories.count; i++) {
        NSString *name = [[allCategories objectAtIndex:i] objectForKey:@"Category"];
        NSString *letter = [name substringToIndex:1];
        letter = [letter uppercaseString];

        if (isdigit([letter characterAtIndex:0]))
              letter = @"#";

        NSMutableArray *section = [map objectForKey:letter];
        if (!section) {
            section = [NSMutableArray array];
            [map setObject:section forKey:letter];
        }
        [section addObject:[allCategories objectAtIndex:i]];
    }

    [_items release];
    _items = [[NSMutableArray alloc] init];
    [_sections release];
    _sections = [[NSMutableArray alloc] init];

    NSArray* letters = [map.allKeys sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
    for (NSString* letter in letters) {
        NSArray* items = [map objectForKey:letter];
        [_sections addObject:letter];
        [_items addObject:items];
    }
}

Now, displaying items in tableView, use below methods -

#pragma mark -
#pragma mark Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView {
    if (_sections.count)
        return _sections.count;
    else
        return 1;
}

- (NSInteger)tableView:(UITableView*)tableView sectionForSectionIndexTitle:(NSString *)title
               atIndex:(NSInteger)index {

    if (tableView.tableHeaderView) {
        if (index == 0) {
            [tableView scrollRectToVisible:tableView.tableHeaderView.bounds animated:NO];
            return -1;
        }
    }

    NSString* letter = [title substringToIndex:1];
    NSInteger sectionCount = [tableView numberOfSections];
    for (NSInteger i = 0; i < sectionCount; i++) {
         NSString* section = [tableView.dataSource tableView:tableView titleForHeaderInSection:i];
         if ([section hasPrefix:letter]) {
             return i;
         }
    }

    if (index >= sectionCount) {
        return sectionCount-1;
    } else {
        return index;
    }
}


- (NSArray*)lettersForSectionsWithSearch:(BOOL)withSearch withCount:(BOOL)withCount {
    if (isSearching)
        return nil;

    if (_sections.count) {
        NSMutableArray* titles = [NSMutableArray array];
        if (withSearch) {
            [titles addObject:UITableViewIndexSearch];
        }
        for (NSString* label in _sections) {
            if (label.length) {
                NSString* letter = [label substringToIndex:1];
                [titles addObject:letter];
            }
        }
        if (withCount) {
            [titles addObject:@"#"];
        }
        return titles;
    } else {
        return nil;
    }
}


- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return [self lettersForSectionsWithSearch:YES withCount:NO];
}


- (NSInteger)tableView:(UITableView *)aTableView numberOfRowsInSection:(NSInteger)section {
    if (_sections.count) {
        NSArray* items = [_items objectAtIndex:section];
        return items.count;
    } else {
        return _items.count;
    }
}


- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    if (_sections.count) 
        return [_sections objectAtIndex:section];

    return nil;
}


- (id)tableView:(UITableView *)tableView objectForRowAtIndexPath:(NSIndexPath *)indexPath {
    if (_sections.count) {
        NSArray *section = [_items objectAtIndex:indexPath.section];
        return [section objectAtIndex:indexPath.row];
    } else {
        return [_items objectAtIndex:indexPath.row];
    }
}


- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    // Create your UITableViewCell.

    // Configure the cell.
    NSDictionary *dict = [self tableView:tableView objectForRowAtIndexPath:indexPath];
    cell.textLabel.text = [dict objectForKey:@"Category"];
            cell.detailTextLabel.text = [NSString stringWithFormat:%d, [[dict objectForKey:@"CID"] intValue]];
    return cell;
}

#pragma mark -
#pragma mark Table view delegate


- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
    if (isSearching)
        return nil;

    NSString *title = @"";
    if (_sections.count) {      
        title = [[_sections objectAtIndex:section] substringToIndex:1];
    } else {
        return nil;
    }

    UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 20)];
    view.backgroundColor = [UIColor colorWithRed:(58/255.0) green:(27/255.0) blue:(6/255.0) alpha:1.0];

    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 1, 50, 18)];
    label.textColor = [UIColor whiteColor];
    label.backgroundColor = [UIColor clearColor];
    label.font = [UIFont boldSystemFontOfSize:17.0];
    label.text = title;

    [view addSubview:label];
    [label release];

    return [view autorelease];
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSDictionary *dict = [self tableView:tableView objectForRowAtIndexPath:indexPath];

    NSLog(@"selected row id:%d, name:%@", [dict objectForKey:@"Category"], [[dict objectForKey:@"CID"] intValue]);
}

The rest part is implementing the UISearchBarDelegate and implementing searching of tableView which can be done using below code:

- (void)searchBar:(UISearchBar *)searchbar textDidChange:(NSString *)searchText {

    [_sections removeAllObjects];
    [_items removeAllObjects];

    if([searchText isEqualToString:@""] || searchText == nil) {
        [self rebuildItems];
        return;
    }

    NSInteger counter = 0;
    for(NSDictionary *dict in allCategories) {
        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
        NSRange r = [[dict objectForKey:@"Category"] rangeOfString:searchText options:NSCaseInsensitiveSearch];
        if(r.location != NSNotFound) {
            if(r.location == 0) {
                [_items addObject:dict];
            }
        }
        counter++;
        [pool release];
    }

    [contactList reloadData];
}

Hope this is what you're looking for.

Sagar
  • 3,149
  • 3
  • 27
  • 28
  • I'm getting a lot of errors from your code, heres some of them... 'NSMutableDictionary' may not respond to '-addObject:', 'NSDictionary' may not respond to '-objectAtIndex:', '_items' undeclared (first use in this function), '_sections' undeclared (first use in this function), 'isSearching' undeclared (first use in this function), error: 'cell' undeclared (first use in this function), may not respond to '-refreshContactsList', error: 'allContacts' undeclared (first use in this function), error: 'contactList' undeclared (first use in this function) – Jules Jun 07 '11 at 08:09
  • I thought it will be understood that _sections & _items are global objects of kind NSMutableArray. And also replace contactList with the object of your UITableView. Like we understood from your code snippet that allCategories would be a property object & must have been synthesized. – Sagar Jun 07 '11 at 08:37
  • NSMutableArray *arrayTmp = [[NSMutableArray alloc] init]; already defined in the code in the start. Also, fix the error of method call of refreshContactsList & allContacts and replaced it with appropriate call. isSearching is again a global boolean. It should be set when searching is started from UISearchBar. – Sagar Jun 07 '11 at 08:42
  • Also, I did mentioned the comment that // Create your UITableViewCell. i.e. create UITableViewCell *cell as per standard method. – Sagar Jun 07 '11 at 08:46
  • I am not getting why/what you're asking for this. NSMutableArray *arrayTmp = [[NSMutableArray alloc] init]; - line is added since my first answer. You can check it from edit history. – Sagar Jun 07 '11 at 08:57
  • btw please don't use things like ?? in your question. We are here to help you as volunteers, not as your employees. ?? won't make your questions any attractive. – Sagar Jun 07 '11 at 08:58
  • [arrayTmp addObject:dict]; has to be kept as it is. Because in the start of the code snippet, it's written that const char *sql = "select cid, category from Categories ORDER BY category DESC"; sqlite3_stmt *statementTMP; NSMutableArray *arrayTmp = [[NSMutableArray alloc] init]; – Sagar Jun 07 '11 at 09:11
  • Do you have taken arrayTmp & allCategories object as NSMutableDictionary. They have to be NSMutableArray objects. allCategories would be a global & arrayTmp should be a local. – Sagar Jun 07 '11 at 09:19
  • Ahhhh sorry, I missed that part, I'll try building the code up now :( – Jules Jun 07 '11 at 09:19
  • You can get the cid value by storing it in allCategories. And how could you tell you what should be in didSelectRowAtIndexPath. It now depends on you what you want to do here. Because it will be called after you touches on any cell. The code which I've provided is only for the configuration of table with sorted values & search code. And regarding back-space, there must be minor error because the above code is taken from the great Three20 library which is working perfectly. – Sagar Jun 07 '11 at 10:02
  • You should ask a separate question for that because the question is only limited to sorting of arrays & I have provided answer not only of sorting the array but also displaying it properly in tableView. So it's a time to give my answer correct because this is what you want. – Sagar Jun 07 '11 at 11:23