iOS Quick Tip – Viewing Core Data’s Generated SQL

If you are using Core Data – or any ORM or persistence framework, for that matter – it’s often handy to be able to view the generated SQL for your queries. Fortunately, Core Data makes this incredibly easy, and unlike some other ORMs, it doesn’t require crazy hacks or third party tools.

Click your project’s name in the Xcode toolbar:

Select ‘Edit Scheme’:

Go to the ‘Arguments’ tab and click the + button under ‘Arguments passed on launch’:

Enter ‘-com.apple.CoreData.SQLDebug 1’ as the argument:

Press OK, and run your app.

Notice all of the SQL statements that appear in Xcode’s output window.

Stylish looking glossy buttons with UIButton+Glossy

You’ve all seen the standard, iOS buttons. Although functional, they are, well, plain, to put it mildly.

Perhaps you’ve even tried to make some prettier buttons in photoshop. But if you’re like me, and you have no artistic skills, this can be a challenge. Fortunately, Michael Heyeck has put together a slick little piece of code for generating stylish, glossy buttons.

It’s ridiculously easy to use, and the results look great.

Step 1 – download UIButton+Glossy.h and UIButton+Glossy.m and stick them in your project.

Step 2 – #import “UIButton+Glossy.h” in the view controller that holds your buttons.

Step 3 – style the buttons! This is done by calling the setBackgroundToGlossyRectOfColor:withhBorder:forState method. The color parameter specifies the button’s color. The withBorder parameter specifies whether or not the button should have a border, and the forState parameter is the UIControlState that the style should be applied to.

You can put this code in your view controller’s viewDidLoad method. So, given a UIButton* named button, we can style it like so:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    [button setBackgroundToGlossyRectOfColor:[UIColor colorWithRed:.05 green:.65 blue:.05 alpha:1] withBorder:NO forState:UIControlStateNormal];
}

And with that, you have a stylish looking button!
A little style can go a long way into helping your app stand out from the crowd.

Download the Sample Project

In-App Rating Prompts With iRate

I recently updated all of my apps to include in-app rating prompts using Nick Lockwood’s ridiculously awesome iRate library. It will be a while before I find out if this results in an increase in sales, but for now, here’s a quick guide to using iRate.

Step 1: Download iRate.h and iRate.m and include them in your project.

That’s it! You’re ready to go. Of course, you may want to do a bit of extra configuration, but iRate’s defaults are set up in such a way that you might not even need to. Nevertheless, you’ll probably want to at least turn on iRate’s debug flag so that it prompts you for a rating every time you launch the app. This lets you see that the rating prompt and the link to the app store are working correctly. To do that, just add the following to your app delegate:

#import "iRate.h"

...

+ (void)initialize
{
    [iRate sharedInstance].debug = YES;
}

Now, if you run your app, you’ll see the rating prompt. Note that if you are running in the simulator, the ‘rate’ link won’t actually work – it only seems to work on a device. Just don’t forget to turn off the debug flag before you ship your app!

By default, the rating prompt will appear after the app has been installed for 10 days, and has been used 10 times. If you want to change these defaults (I switched them to 5 and 5), you can do so in the initialize method mentioned above.

+ (void)initialize
{
    [iRate sharedInstance].daysUntilPrompt = 5;
    [iRate sharedInstance].usesUntilPrompt = 5;
}

iRate offers an absurd number of configuration options, so no matter what your scenario is, it’s probably doable. Check out the documentation for all the details.

Good luck on getting those downloads and ratings!

Filtering a UITableView with a UISearchBar using Core Data

Continuing the tradition of UITableView filtering articles on this blog, I thought it would be fun to do one on filtering with Core Data.

This won’t be a Core Data tutorial though, so I won’t be going in to much detail on how Core Data itself works, or how to set it up.

We’ll start off with the UITableView filtering sample from my original blog post. We’ll then create a very simple object model containing our Food class, and give it a name and a desc property.

<insert boring Core Data boilerplate code here>

Now that that’s all done, we’ll start off by populating our data model with some default foods. We can do that in our App Delegate. We want to make sure the initial data is only added once, so we’ll use a key in NSUserDefaults to keep track of whether or not we’ve added our data yet. So we’ll add some code to application:didFinishLaunchingWithOptions.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
    bool hasAddedData =  [prefs boolForKey:@"HasAddedInitialData"];
    if(!hasAddedData)
    {
        [self addInitialData];
        [prefs setBool:true forKey:@"HasAddedInitialData"];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }

    return YES;
}

We call the addInitialData method if we haven’t done it before. The addInitialData method looks like this:

-(void)createFoodWithName:(NSString*)name andDescription:(NSString*)description
{
    Food* food = (Food*)[NSEntityDescription insertNewObjectForEntityForName:@"Food" inManagedObjectContext:self.managedObjectContext];
    food.name = name;
    food.details = description;
}

-(void)addInitialData
{
    [self createFoodWithName:@"Steak" andDescription:@"Rare"];
    [self createFoodWithName:@"Steak" andDescription:@"Medium"];
    [self createFoodWithName:@"Salad" andDescription:@"Caesar"];
    [self createFoodWithName:@"Salad" andDescription:@"Bean"];
    [self createFoodWithName:@"Fruit" andDescription:@"Apple"];
    [self createFoodWithName:@"Potato" andDescription:@"Baked"];
    [self createFoodWithName:@"Potato" andDescription:@"Mashed"];
    [self createFoodWithName:@"Bread" andDescription:@"White"];
    [self createFoodWithName:@"Bread" andDescription:@"Brown"];
    [self createFoodWithName:@"Hot Dog" andDescription:@"Beef"];
    [self createFoodWithName:@"Hot Dog" andDescription:@"Chicken"];
    [self createFoodWithName:@"Hot Dog" andDescription:@"Veggie"];
    [self createFoodWithName:@"Pizza" andDescription:@"Pepperonni"]; 

    [self.managedObjectContext save:nil];
}

It simply populates the data model with our default foods.

Up next comes the changes to our UITableViewController. We’ll change the viewDidLoad method so that it looks like this. The primary difference between the old version and this version is that this version sets up its managedObjectContext so that it can make Core Data calls. It also calls the shiny new filter method that does the actual filtering.

- (void)viewDidLoad
{
    [super viewDidLoad];

    UITableViewFilterDemoAppDelegate* del = [UIApplication sharedApplication].delegate;
    self.managedObjectContext = del.managedObjectContext;

    searchBar.delegate = (id)self;

    [self filter:@""];
}

The filter method is where we do the real work.

-(void)filter:(NSString*)text
{
    filteredTableData = [[NSMutableArray alloc] init];

    // Create our fetch request
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

    // Define the entity we are looking for
    NSEntityDescription *entity = [NSEntityDescription
                                   entityForName:@"Food" inManagedObjectContext:managedObjectContext];
    [fetchRequest setEntity:entity];

    // Define how we want our entities to be sorted
    NSSortDescriptor* sortDescriptor = [[NSSortDescriptor alloc]
                                        initWithKey:@"name" ascending:YES];
    NSArray* sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil];
    [fetchRequest setSortDescriptors:sortDescriptors];

    // If we are searching for anything...
    if(text.length > 0)
    {
        // Define how we want our entities to be filtered
        NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(name CONTAINS[c] %@) OR (details CONTAINS[c] %@)", text, text];
        [fetchRequest setPredicate:predicate];
    }

    NSError *error;

    // Finally, perform the load
    NSArray* loadedEntities = [managedObjectContext executeFetchRequest:fetchRequest error:&error];
    filteredTableData = [[NSMutableArray alloc] initWithArray:loadedEntities];

    [self.tableView reloadData];
}

It’s a little complicated, but the comments essentially describe the flow. The important part – the actual filtering – is the NSPredicate* part. We create a predicate which says we want to find all foods with either a name or details that contains our search string. The [c] is used to indicate that the match be case insensitive.

The results of the filtering are stored in the class’s filteredTableData object.

Finally, there are a few more details we have to worry about. We’ll have to add our usual UITableView data source methods. The methods simply return the appropriate data from the filteredTableData array. We also want to set up the textDidChange searchBar delegate method so that it calls our filter method.

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    int rowCount = filteredTableData.count;
    return rowCount;
}

- (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];

    Food* food = [filteredTableData objectAtIndex:indexPath.row];
    cell.textLabel.text = food.name;
    cell.detailTextLabel.text = food.details;

    return cell;
}

-(void)searchBar:(UISearchBar*)searchBar textDidChange:(NSString*)text
{
    [self filter:text];
}

And there you have it – a UISearchBar filtering a UITableView with the power of Core Data!

Download the demo project

 

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 = food.name;
    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.

-(void)updateTableData:(NSString*)searchString
{
    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;
        }
        else
        {
            // If we have a search string, check to see if it matches the food's name or description
            NSRange nameRange = [food.name 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...
        if(isMatch)
        {
            // Find the first letter of the food's name. This will be its gropu
            NSString* firstLetter = [food.name 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

iOS Quick Tip – Core Data and “unrecognized selector sent to instance 0xsomething”

While experimenting with Core Data, I ran into a little issue with the always annoying ‘unrecognized selector’ exception.
I had created my Core Data model, along with my NSManagedObjectContext classes, and I was trying to use them like this:

Foo* foo = [[Foo alloc] init];
foo.bar = @"This line will crash!";

… which of course results in the aforementioned exception.

This is what happens when you don’t read the documentation. It quite clearly states that you pretty much have to use NSEntityDescription’s insertNewObjectForEntityForName:inManagedObjectContext method to create your objects.

And sure enough, if you create your objects like this instead:

Foo* foo = [NSEntityDescription insertNewObjectForEntityForName:@"Foo" inManagedObjectContext:context];
foo.summary = @"This is a bug";

it works as expected.

That said, making a class called ‘NSEntityDescription’ responsible for creating objects and adding them to a context seems like a somewhat dubious design decision.