iOS App Postmortems – Drink Menu

Drink Menu is now available on the app store. Like always, that means it’s postmortem time!

Development Time:

  • ~3h. Drink Menu was essentially a total conversion of BBQ Menu, so I wasn’t starting from scratch.

Review Time (time spent in the iOS app submission queue):

New technologies I learned and used:

  • There wasn’t really anything new here


  • Fully renaming an Xcode project is surprisingly difficult. There are a lot of hidden strings and plist entries to watch out for. Other than that, it was smooth sailing.

Future plans:

Drink Menu, along with BBQ Menu, do pretty much everything I want them to do. Unless there are specific requests from users, they likely won’t get too many more features. So as always, please let me know if there’s anything you would like to see in it!

iOS Pain Points – the App Review Waiting Game

My latest app was uploaded on July 9. It’s now July 19 – almost two full weeks worth of business days later – and approval is nowhere to be found. Granted, I’ve had pretty good luck so far, with most of my apps being approved in a week or so, but this is getting annoying.

What exactly am I paying $99 per year for? Forced upgrades, woefully inadequate development tools, constantly changing rules, a black-box review process? In short, why is Apple’s entire developer ecosystem so incredibly mediocore?

Am I wrong to expect better from one of the richest companies in the history of the world? Why can’t they hire a few more temps to help get through the app backlog and speed up the review times?

I believe that to a certain extent, Apple itself has caused many of these problems themselves , as the current app store rules encourage quick hacks and one-and-done applications.

As always, none of this matters, as Apple is simply not interested in the opinions of a small-time app developer. They are content to sit on their laurels and rake in their billions.

(And yes, there’s definitely a lot of bitterness in this post. Sorry.)

EDIT: Ironically, my app was approved the day after posting this. Thanks for listening, Apple!

Filtering a Grouped UITableView with a Search Bar

I’ve mentioned before that my post on Filtering a UITableView with a Search Bar has been by far my most popular post. A natural extension of that post – and something I’ve had a few questions about – is how to do the same thing for a grouped UITableView.

This post will demonstrate filtering a food list that is grouped by the first letter of each food’s name.

So let’s start out with the same sample application used in the original post. We’ll be storing our filtered data in a dictionary, with the keys being letters and the values being NSArrays containing all of the foods for each letter. We’ll also keep an array of our dictionary’s keys, sorted alphabetically. This is needed because NSDictionary is an unordered collection, and without it, our groups wouldn’t be in alphabetical order.

First, we’ll define the data structures we’ll be using in our header file.

@property (strong, nonatomic) NSArray* allTableData;
@property (strong, nonatomic) NSMutableDictionary* filteredTableData;
@property (strong, nonatomic) NSMutableArray* letters;

Next, we’ll have to implement our usual table view data source methods, but this time, we’ll be reading the values to return from our dictionary. For numberOfSectionsInTableView, we’ll return the number of items in our dictionary, and for numberOfRowsInSection, we’ll return the number of items in each section’s array (remember, our dictionary one array per section/letter).

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    // Return the number of letters in our letter array. Each letter represents a section.
    return letters.count;

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    // Returns the number of items in the array associated with the letter for this section.
    NSString* letter = [letters objectAtIndex:section];
    NSArray* arrayForLetter = (NSArray*)[filteredTableData objectForKey:letter];
    return arrayForLetter.count;

We’ll also need to implement one additional method – titleForHeaderInSection. It will return the key corresponding to the specified section.

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
    // Each letter is a section title
    NSString* key = [letters objectAtIndex:section];
    return key;

Our cellForRowAtIndexPath method has a few little changes. It has to look up the NSArray corresponding to the specified section, then find the Food object inside of the NSArray. So it now looks something like this:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    static NSString *CellIdentifier = @"Cell";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil)
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];

    // Grab the object for the specified row and section
    NSString* letter = [letters objectAtIndex:indexPath.section];
    NSArray* arrayForLetter = (NSArray*)[filteredTableData objectForKey:letter];
    Food* food = (Food*)[arrayForLetter objectAtIndex:indexPath.row];

    cell.textLabel.text =;
    cell.detailTextLabel.text = food.description;

    return cell;

That’s all well and good, but how do we actually set up our dictionary? How do we actually, you know, filter stuff?

The updateTableData method takes care of this. This method loops through each Food object to see if it matches our search string. If it does, the code checks to see if there is an existing NSMutableArray for its first letter. If so, the Food object is added to the existing array. if not, a new NSMutableArray is created and added to the dictionary, and the Food object is added to it.

    filteredTableData = [[NSMutableDictionary alloc] init];
    for (Food* food in allTableData)
        bool isMatch = false;
        if(searchString.length == 0)
            // If our search string is empty, everything is a match
            isMatch = true;
            // If we have a search string, check to see if it matches the food's name or description
            NSRange nameRange = [ rangeOfString:searchString options:NSCaseInsensitiveSearch];
            NSRange descriptionRange = [food.description rangeOfString:searchString options:NSCaseInsensitiveSearch];
            if(nameRange.location != NSNotFound || descriptionRange.location != NSNotFound)
                isMatch = true;
        // If we have a match...
            // Find the first letter of the food's name. This will be its gropu
            NSString* firstLetter = [ substringToIndex:1];
            // Check to see if we already have an array for this group
            NSMutableArray* arrayForLetter = (NSMutableArray*)[filteredTableData objectForKey:firstLetter];
            if(arrayForLetter == nil)
                // If we don't, create one, and add it to our dictionary
                arrayForLetter = [[NSMutableArray alloc] init];
                [filteredTableData setValue:arrayForLetter forKey:firstLetter];
            // Finally, add the food to this group's array
            [arrayForLetter addObject:food];
    // Make a copy of our dictionary's keys, and sort them
    letters = [[filteredTableData allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
    // Finally, refresh the table
    [self.tableView reloadData];

We’ll also need to take care of a few little details, such as setting up our initial data structures, and hooking up our updateTableData method to the search bar.

- (void)viewDidLoad
    [super viewDidLoad];

    searchBar.delegate = (id)self;
    allTableData = [[NSArray alloc] initWithObjects:
        [[Food alloc] initWithName:@"Pizza" andDescription:@"Pepperonni"], 
        [[Food alloc] initWithName:@"Fruit" andDescription:@"Apple"], 
        [[Food alloc] initWithName:@"Apple" andDescription:@"Red"], 
        [[Food alloc] initWithName:@"Apple" andDescription:@"Green"], 
        [[Food alloc] initWithName:@"Bread" andDescription:@"Brown"], 
        [[Food alloc] initWithName:@"Hot Dog" andDescription:@"Beef"], 
        [[Food alloc] initWithName:@"Hot Dog" andDescription:@"Chicken"],
        [[Food alloc] initWithName:@"Potato" andDescription:@"Baked"], 
        [[Food alloc] initWithName:@"Potato" andDescription:@"Mashed"], 
        [[Food alloc] initWithName:@"Hamburger" andDescription:@"Beef"], 
        [[Food alloc] initWithName:@"Steak" andDescription:@"Rare"], 
        [[Food alloc] initWithName:@"Steak" andDescription:@"Medium"], 
        [[Food alloc] initWithName:@"Salad" andDescription:@"Caesar"], 
        [[Food alloc] initWithName:@"Salad" andDescription:@"Bean"], 
        nil ];
    [self updateTableData:@""];

-(void)searchBar:(UISearchBar*)searchBar textDidChange:(NSString*)text
    [self updateTableData:text];

Once all that is complete, we’ll have a filterable, grouped UITableView. Enjoy! As a side note, I’m sure there are plenty of other ways to accomplish this task, but this method is relatively simple and it seems to do the trick.

Download The Sample Project