Mission: Minesweeper 2.0

Mission: Minesweeper 2.0

I’ve completed the 2.0 version of my popular Mission: Minesweeper game and it is now available on the App Store. Please feel free to go check it out, I’ll wait.

The game has been fully updated to a universal application and looks great (if I do say so myself) on the iPhone, iPod Touch and the iPad. In reality, the interface is so different between the iPhone and iPad versions that it is practically two different applications squeezed together into one binary. The iPhone version uses the the typical navigation controller for all of it’s navigation while the iPad version uses popovers instead.

In fact, the iPad version is also using my BAMPopoverBackgroundView which I will be making publicly available soon. It is a subclass of UIPopoverBackgroundView and makes it very simple to change the appearance of your popovers by changing the tintColor or images associated with the popover. I think everyone will be pleased with it.

Of course, I also took the opportunity to include images optimized for retina displays while I was updating it. This was a rather large undertaking since the app was originally written for older iPhones. That means I had to recreate and rerender every single image used for each device. As well as generating over thirty images for use in the new achievements.

Achievements, Lots of Achievements

Achievements, Lots of Achievements

Game Center support was also added to the game, which now has three different leaderboards: Beginner, Intermediate and Advanced. Achievements were also added. There are thirty-one achievements in all and I’ll be surprised if anyone ever earns the “Impossible Mission” achievement which requires winning a game by uncovering a cell surrounded by eight other cells. Pretty much an impossible feat of both skill and luck!

On the road to adding Game Center to my app, it occurred to me that most people are using the GameCenterManager from Apple’s GKTapper demo to incorporate Game Center into their apps. It seems to me that it could be improved upon greatly by adding a queue to ensure that achievements are never lost because a phone call interrupted the process or the app was terminated.

Additionally, it would be nice to have the user notifications built into the class, as well, so that by simply adding the class and submitting the achievements, all of the local queueing and user notification tasks would be handled automatically. If people are interested in seeing a future BAMGameCenterManager class, let me know and I’ll see about creating it once I get a bit of free time.

But for now, your mission should you decide to accept it…

Leave a comment Posted in Mission: Minesweeper, My Apps | Tagged , , , |

Check Split Sneak Peek

Check Split Screenshot

Well, I’ve finally completed my Check Split app and I’m waiting for Apple to approve it. As you can see, it is a universal app which runs on the iPad, iPhone and iPod Touch.

It’s unlike any of the existing tip calculators or check splitters currently on the App Store. Rather than simply calculating the tip and splitting it equally for each person, Check Split allows you to enter an amount for any diner and it will then split the remaining amount equally among the other diners. So, now when someone protests that they should pay less because they didn’t order any drinks or when another person simply tosses in $40 and says “that will cover mine,” you’ll be able to quickly divvy up the rest.

You can enter a name and photo for any diner if you like. You can even select from your address book or Facebook friends. If you’re a developer, you will note that I’ve created a friend picker that looks like Apple’s address book picker. Eventually, I’ll release that but I have a couple of other projects I need to complete before I can complete my Facebook UI framework.

Please check out my Check Split page on AppsUtopia.com for even more details and screenshots. If you find any typos or other mistakes in the description, please let me know.

Now, for those of you who remember my icon dilemma and voted to help me pick an icon, I’m revealing the final icon.

Check Split Icon

When I tallied the votes for the previous icons, there was no consensus. Some people liked the symbolic split check while others preferred the more literal icons. Some people liked very simple designs while others preferred the cluttered icons. So it was clear that I needed to do more work to make the icon right. In the end, I’ve created an icon that keeps the clutter but darkened it so it remains in the background even at smaller sizes while placing a symbolic split check over the top.

I think this is the best of the icons and I hope you think so, too.

Thanks to everyone for helping me decide and I look forward to the time when I can tell you the app is finally on the App Store for download.

1 Comment Posted in Check Split, My Apps | Tagged , , , , |

Poor Quality from Zazzle

Well, I thought it would be fun to create a few tee shirts and I went to Zazzle to have them printed. Here’s what happened.

The Original Designs

I had three different designs that I had printed. One black shirt with printing on the front and back, one grey shirt with printing only on the front, and a ladies long-sleeved olive tee with printing on the front. Everything looked great on Zazzle’s design tool as shown below.

The Black Shirt

The Black Shirt

The Grey Shirt

The Grey Shirt

The Ladies' Shirt

The Ladies' Shirt

But once the shirts arrived, they turned out to have problems.

Poor Printing

The printing on the black shirt was especially bad with small pinholes throughout the design on both the front and the back of the shirt. In the images below (click to enlarge) you can see the holes in the ink.

Bad Printing

Halos

Every shirt also had unsightly white halos around black black elements.

HaloMore Halos

Sizing

Size TagsThere was also a huge sizing difference between the black and grey shirts. On the Zazzle site, these are both the same shirt, just different colors. According to the tags on the shirts, they are the same manufacturer and the same size and the Zazzle web site says the “sizes run standard.”

However the actual shirts are very different. The black shirt, when laid flat, is about 24½ inches (62 cm) Size Differenceswhile the grey shirt is closer to 23 inches (59 cm) across. Of course, the difference in circumference is twice as great since this measurement was taken across the flat tee shirt. In the image to the right, both shirts are aligned evenly along the left edge but you can see how different the widths are. As a result, the grey shirt is tighter than I would like.

You can also see that the grey shirt has a clear overlay of some sort that makes the shirt significantly darker in the imprint rectangle. All the shirts have this but is very obvious on the grey shirt. Sadly, it makes the grey shirt look as if I made it myself with an iron-on or something.

The Ladies’ Tee

And to round out the Zazzle fuck-up trifecta, I present the ladies’ tee shirt which has the imprint skewed downhill and not even close to being centered. And just to add insult to injury, there’s a hole in the seam for the right sleeve.

Crooked PrintingWith a Hole

Customer Support

Okay, so anyone is entitled to a bad day. Perhaps the Zazzle employee who printed my items was new or otherwise off their game. So I contacted Zazzle’s customer service with a return request and was informed that I had two options:

  • Zazzle Account Credit or refund for the product (so that you may place a new order on the site)
  • A replacement for the product (using the current design/content) can be created on a different size, style, or colored* item of the same product type.

No acknowledgment of the error. No indication of whether these problems are to be expected in the reprinted items. No assurances that they can fix anything. And, finally, no option to have my money returned to me. Even though their web site has the following on it:

The Zazzle Promise: If you don’t love it, we’ll take it back. If you are not 100% satisfied, you can return it for a replacement or refund within 30 days of receipt.

Needless to say, I am very unhappy with Zazzle and do not recommend them to anyone seeking a custom printed tee shirt.

1 Comment Posted in Graphic Design | Tagged |

BAMEasyTable Demo

BAM Easy Table Demo

What Is BAMEasyTable?

BAMEasyTable is a subclass of UITableViewController that makes it easy to implement powerful tables in your app with minimal coding. This post explains how to use BAMEasyTable. For additional information please see my post introducing BAMEasyTable.

Using BAMEasyTable

BAMEasyTable was designed to be very easy to implement in your app. To use BAMEasyTable in your app, simply copy BAMEasyTable.h and BAMEasyTable.m into your project and include BAMEasyTable.h in your view controller like this:

#include "BAMEasyTable.h"

Load BAMEasyTable with Data to Display

After initializing BAMEasyTable, either as UITableViewStylePlain or UITableViewStyleGrouped, you’ll need to load it with data to display. There are two different methods available for you can to use:

loadTableFromArray:

Allows you to specify an array of NSStrings or custom objects containing your data. This will produce a simple table that displays your data in a single section.

loadTableFromArrayOfArrays:

Allows you to specify an array of arrays that will be rendered as sections in your table. Each section will have it’s own array of NSStrings or custom data objects.

Add BAMEasyTable to Your View

Now you just need to add the BAMEasyTable to your view. You can use BAMEasyTable’s view anywhere you can use a UITableView but in my example I’m pushing it onto a navigation controller. Of course, you will need to determine the best usage for your own app but let’s assume we’ll do this after a user presses a button like this:

1
2
3
4
5
6
7
8
- (IBAction)buttonPressed:(id)sender {
    NSArray *yourArray = [[NSArray alloc] initWithObjects:@"Item One", @"Item Two", nil];
    BAMEasyTable *bamEasyTable = [[BAMEasyTable alloc] initWithStyle:UITableViewStylePlain];
    [bamEasyTable loadTableFromArray:yourArray];
    [self.navigationController pushViewController:bamEasyTable animated:YES];
    [bamEasyTable release];
    [yourArray release];
}

That’s All!

That’s all you need to do. When the user clicks the button your table will display loaded with the items in the array. They will be able to search the table with a search bar in the header and a footer will show how many items are in the table.

Simple Table Example

BAM Easy Table Simple Table Example

In the demo app, which you can download at the end of this post, there are several examples of BAMEasyTable in action. The first is a very simple demo which does exactly what I’ve shown you here.

Just initialize BAMEasySettings, load it with an array and then push it onto a navigation controller. In this case, I’m displaying a list of fictional beverages.

Already, you can search through the items by using the search box at the top and you can see that we have 46 items in the table.

Go ahead and check it out.

Grouped Table Example

BAM Easy Table Grouped Table Example

The next example in the demo app is a grouped table. It work’s in a similar way to the previous example but we use an array of arrays and we pass in an array for the group titles. This is just a simple array of NSStrings and which we pass to the headerTitles property on our BAMEasyTable instance.

However, in this example we also do a couple of other things I’d like to point out. First, we now assign our calling class as the delegate so that we can tell when an item has been selected. You can learn more about the delegate in the BAMEasyTable Delegate post.

Also, we pass in singular an plural labels to use in the counter footer. Since our list is of Victoria’s Secret models, I think they’d dislike being called “items” which is the default value so we tell BAMEasyTable to use “model” and “models” respectively.

1
2
3
4
5
6
7
8
9
10
BAMEasyTable *bamEasyTable = [[BAMEasyTable alloc] initWithStyle:UITableViewStyleGrouped];
bamEasyTable.delegate = self;
bamEasyTable.headerTitles = titles;
bamEasyTable.countLabelTextSingular = @"Model";
bamEasyTable.countLabelTextPlural = @"Models";
[bamEasyTable loadTableFromArrayOfArrays:victoriasSecretModelsByCountry];
[self.navigationController pushViewController:bamEasyTable animated:YES];
[bamEasyTable release];
[victoriasSecretModelsByCountry release];
[titles release];

It is important that you always set BAMEasyTable’s properties before loading your table. Once the table is loaded any changes made to BAMEasyTable’s properties may be ignored or cause inconsistencies in the table’s structure. So, always set your properties prior to calling the loadTableFromArray: or loadTableFromArrayOfArrays: methods.

Indexed Table Example

BAM Easy Table Indexed Demo

The indexed demo works much like the grouped demo. We still use our array of arrays and headerTitles array but now we also pass in an indexTitles array. This array contains the titles to be used in the index on the righthand side of the table. We pass it in like this:

bamEasyTable.indexTitles = indexTitles;

Now we’ll have an index to allow users to quickly jump to the corresponding section of the table. But wait, in the previous example we set a delegate and we still haven’t implemented any delegate methods. How exactly do we know when a user has selected a row?

1
2
3
4
5
6
- (void)bamEasyTable:(BAMEasyTable *)easyTable didSelectObject:(id)selectedObject {
    NSString *selectedModel = (NSString *)selectedObject;
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:selectedModel message:@"was selected." delegate:nil cancelButtonTitle:@"Yeah, I know" otherButtonTitles:nil];
    [alert show];
    [alert release];
}

By simply adding the bamEasyTable:didSelectObject: method into our code we will now be notified whenever the user taps on a row. For good measure, you are also given the id of the object that corresponds to this row so that you can easily use it when you respond. In this example, I’m simply taking the string value and showing it in an alert but you can use it however you like.

Detail Text and Images

BAM Easy Table Detail Text and Images Demo

I can already hear some of you complaining, “But my data is much more complicated than an array of strings.”

Well, we’ve only just begun to scratch the surface of BAMEasyTable. You can use your own custom data objects in your arrays instead of NSStrings. So any you can pass in any NSObject your little heart desires. The only limitation is that your object must have some method that returns a string value that you wish to have displayed.

To tell BAMEasyTable about the methods on your object you just pass in the name of the method that should be used to retrieve the necessary information. If you have an NSString stored in your object, and you’ve added a @property and @synthesize for it, then you already have a method that returns the string. By default, that method has the same name as the variable so you can just use that for the method name.

bamEasyTable.textStringMethodName = @"name";
bamEasyTable.detailStringMethodName = @"termString";

You can set a method to retrieve values for both the primary text in the table cell as well as the detail text. In addition, you can set specify a method which retrieves a UIImage to be used in the cell’s image view.

bamEasyTable.imageMethodName = @"icon";

Of course, you may need to change the cell style to accommodate your needs. Apple’s default cell style does not have detail text.

bamEasyTable.cellStyle = UITableViewCellStyleSubtitle;

You can specify any UITableViewStyle you like. Apple’s cell styles are UITableViewStyleDefault, UITableViewStyleValue1, UITableViewStyleValue2 and UITableViewStyleSubtitle.

Adding and Removing Items

BAM Easy Table Adding and Removing Items Demo

Of course, you may need to do more than just select items from a table. You may need to add or delete items. BAMEasyTable handles this for you, too. However, you are responsible for maintaining your data model. So while BAMEasyTable will add and delete items, you need to endure that you update your data source so that it matches.

First, you’ll need to let BAMEasyTable know what actions you would like to allow:

bamEasyTable.allowRemoving = YES;

Tell BAMEasyTable that you’d like to display an edit button. The default is BAMEasyTableEditButtonTypeNone but to show a button you’ll need to change it to BAMEasyTableEditButtonTypeLeft or BAMEasyTableEditButtonTypeRight.

bamEasyTable.editButtonType = BAMEasyTableEditButtonTypeRight

Finally, if you’d like to allow the user to add items, you should tell BAMEasyTable by setting:

bamEasyTable.showAddButtonWhileEditing = YES;

Now, when a user removes an item from the table, you will be notified via the following delegate method.

- (void)bamEasyTable:(BAMEasyTable *)easyTable didRemoveItemAtIndexPath:(NSIndexPath *)indexPath

Likewise, when the user taps the add button, you’ll be notified by the delegate:

- (void)bamEasyTableAddButtonPressed:(BAMEasyTable *)easyTable

After which, you should present a form allowing the user to create whatever it is you’re displaying in the table. Obviously, since you can provide any custom object to BAMEasyTable, it is impossible for it to manage your data so you must be responsible for creating the necessary object and figuring out where it belongs in the table by calling:

[bamEasyTable insertRowObject:entry atIndexPath:indexPath withRowAnimation:UITableViewRowAnimationRight];

If you need to add a new section to accommodate the added record, you should first call:

[bamEasyTable insertSection:section withHeaderTitle:headerTitle indexTitle:indexTitle footerTitle:footerTitle rowAnimation:UITableViewRowAnimationRight];

Finally, if you’ve simply changed an existing item you can simply reload the appropriate cells by calling:

[bamEasyTable.tableView reloadRowsAtIndexPaths:indexPath withRowAnimation:UITableViewRowAnimationFade];

Changing Search Behavior

The search behavior of BAMEasyTable can alo be customized to your needs. First, if you wish to disable searching you should specify:

bamEasyTable.allowSearching = NO;

But you can also change how he search is performed. By default, BAMEasyTable will search against the beginnings of words to see if there is a match. However it may search just the beginning of the entire string, the beginnings of words, the end of the entire string, the ends of words, or any matching substring within the text. The values to specify are: BAMEasyTableSearchTypeBeginningOnly, BAMEasyTableSearchTypeWordBeginning, BAMEasyTableSearchTypeEndingOnly, BAMEasyTableSearchTypeWordEnding and BAMEasyTableSearchTypeSubstring, respectively.

bamEasyTable.searchType = BAMEasyTableSearchTypeWordBeginning;

Finally, BAMEasyTable will search against the text displayed as the primary text of the cell. However, if you wish to search a different string, you can simply provide the name of a method in your data object that returns an NSString which will be searched against instead. The demo app has an example that can be uncommented in the “Add & Remove Items” example if you’d like to see it in action.

bamEasyTable.searchStringMethodName = @"searchString";

Customizing BAMEasyTable

BAMEasyTable allows you to customize the colors used for the search bar and for the out of bounds area above the table view when searching is allowed.

bamEasyTable.searchHeaderColor = [UIColor darkGrayColor];
bamEasyTable.topBoundsViewColor = [UIColor darkGrayColor];

The count label is also exposed so you can make changes to it:

bamEasyTable.countLabel.textColor = [UIColor darkGrayColor];
bamEasyTable.countLabel.font = [UIFont boldSystemFontOfSize:16.0f];

Or the count footer can be hidden entirely with:

bamEasyTable.showCountInFooter = NO;

Also, since BAMEasyTable is a subclass of UITableViewController, you can use any properties of UITableView to customize your table, too.

bamEasyTable.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
bamEasyTable.tableView.sectionHeaderHeight = 30.0f

Customizing Cells

Most of the time, one of Apple’s default cell styles in adequate for your needs. However, it’s quite common to modify those default cell styles in different ways. Usually, you would modify the cells after creating them. Since BAMEasyTable creates the cells automatically, it provides a delegate method that simply allows you to make modifications to that cell after it’s been created. If you need to customize the cells, you can implement the delegate, like so:

1
2
3
4
5
6
- (void)bamEasyTable:(BAMEasyTable *)easyTable cellForCustomization:(UITableViewCell *)cell withObject:(id)currentObject {
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    cell.selectionStyle = UITableViewCellSelectionStyleGray;
    cell.detailTextLabel.textColor = [UIColor darkGrayColor];
    cell.textLabel.font = [UIFont fontWithName:@"Georgia-Bold" size:18.0f];
}

But if you need completely custom cells, you can create your own by implementing the bamEasyTable:cellForObject: method of BAMEasyTable which works like UITableView’s tableView:cellForRowAtIndexPath: method with the exception that a nil value will simply produce the default cell type for the object. This allows you to only concern yourself with the cells that are actually different from the default if you desire.

- (UITableViewCell *)bamEasyTable:(BAMEasyTable *)easyTable cellForObject:(id)currentObject;

Customizing Headers

You can provide custom views for your headers by implementing a delegate. You will receive a pointer to the title string also to make it as easy as possible to implement.

- (UIView *)bamEasyTable:(BAMEasyTable *)easyTable viewForHeaderInSection:(NSInteger)section withTitle:(NSString *)title;

If you need to change the height of the header you would respond to:

- (CGFloat)bamEasyTable:(BAMEasyTable *)easyTable heightForHeaderInSection:(NSInteger)section;

You can view examples of these methods in the “Add & Remove Items” example of the demo app. Corresponding footer methods also exist. You can get more details from reading the BAMEasyTable Delegate post.

Reordering Items

If your table needs for you to implement cell reordering, first you need to tell BAMEasyTable to allow reordering.

bamEasyTable.allowMoving = NO;

Then tell BAMEasyTable that you’d like to display an edit button.

bamEasyTable.editButtonType = BAMEasyTableEditButtonTypeRight

Now you’ll be notified when the items have been reordered by the user through the delegate:

- (void)bamEasyTable:(BAMEasyTable *)easyTable movedRowFromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath;

As before, you are responsible for keeping your data model updated after the move. An example is provided in the “Detail Text & Images” section of the demo app.

A New Concept: Thresholds

Finally, BAMEasyTable uses thresholds to determine whether to display header titles, footer titles and indexes. When a table has only a few records, the titles and indexes can be more of a distraction than anything else. Seriously, does a plain table with ten items need six sections and indexes?

To make it easier to create a table that only displays these items when there are enough items to warrant their existence, BAMEasyTable uses a threshold to keep the table from displaying the associated titles or index when the number of items is below the threshold.

The default is to use thresholds. However, you may change this behavior by explicitly setting the BAMEasyTableSectionHeaderType, BAMEasyTableSectionFooterType or BAMEasyTableIndexType.

bamEasyTable.indexType = BAMEasyTableIndexTypeThreshold;
bamEasyTable.sectionHeaderType = BAMEasyTableSectionHeaderTypeShow;
bamEasyTable.sectionFooterType = BAMEasyTableSectionFooterTypeNeverShow;

The values are *Threshold, *Show or *NeverShow for each type. By default, titles will show when there are over 20 items and the index will show when there are over 50 items. However, if you would like to change the threshold value you can set it directly.

indexThreshold = 50;
bamEasyTable.sectionHeaderThreshold = 20;
bamEasyTable.sectionFooterThreshold = 20;

A Final Word

I know this tutorial seems long and laborious but that’s because I’m attempting to explain the entire functionality of BAMEasyTable. The reality is that the overwhelming majority of tables used on the iPhone can be implemented by just creating the data array, loading the table and implementing the bamEasyTable:didSelectObject: delegate. What little customization is usually done can easily be accomplished using only the bamEasyTable:cellForCustomization: delegate.

The rest is just for the few times when you need much more customization. You can safely ignore it unless you have a need for it.

Hopefully, you’ll find this class useful in your projects. And, as promised before, the demo can be download below.

Download from GitHub

 
 
 
 

1 Comment Posted in BAMEasyTable, iOS Programming | Tagged , , , |

BAMEasyTable Delegate

BAM Easy Table Delegate

BAMEasyTable

BAMEasyTable is a class which handles the mechanics of a UITableView allowing you to write significantly less code to implement a table. If you’ve just arrived here and don’t know what BAMEasyTable is please check out my BAMEasyTable or BAMEasyTable Demo posts first.

Using the Delegate

To use the delegate, you need to specify your class as the delegate for BAMEasyTable:

1
2
3
4
5
6
7
8
9
- (IBAction)buttonPressed:(id)sender {
    NSArray *yourArray = [[NSArray alloc] initWithObjects:@"Item One", @"Item Two", nil];
    BAMEasyTable *bamEasyTable = [[BAMEasyTable alloc] initWithStyle:UITableViewStylePlain];
    bamEasyTable.delegate = self;
    [bamEasyTable loadTableFromArray:yourArray];
    [self.navigationController pushViewController:bamEasyTable animated:YES];
    [bamEasyTable release];
    [yourArray release];
}

Next, your class needs to conform to the delegate. So you’ll need to place in your header file, like so:

1
2
3
4
5
@interface MySuperCoolClass : UIViewController <BAMEasyTableDelegate> {

}

@end


BAMEasyTable Delegate Methods

Selection Methods

bamEasyTable:didSelectObject:
bamEasyTable:didDeselectObject:

Cell Customization Methods

bamEasyTable:cellForCustomization:withObject:
bamEasyTable:cellForObject:
bamEasyTable:heightForCellWithObject:
bamEasyTable:titleForDeleteConfirmationButtonForRowWithObject:

Button Response Methods

bamEasyTableAddButtonPressed:
bamEasyTable:accessoryButtonTappedForRowWithObject:

Table Row Editing Methods

bamEasyTable:didRemoveItemAtIndexPath:
bamEasyTable:movedRowFromIndexPath:toIndexPath:
bamEasyTable:canEditRowAtIndexPath:
bamEasyTable:canMoveRowAtIndexPath:

Section Editing Methods

bamEasyTable:canRemoveSection:
bamEasyTable:didRemoveSection:

Header and Footer Methods

bamEasyTable:heightForHeaderInSection:
bamEasyTable:viewForHeaderInSection:withTitle:
bamEasyTable:heightForFooterInSection:
bamEasyTable:viewForFooterInSection:withTitle:


bamEasyTable:didSelectObject:

Tells the delegate that the specified row is now selected.

- (void)bamEasyTable:(BAMEasyTable *)easyTable didSelectObject:(id)selectedObject

easyTable
A BAMEasyTable object informing the delegate about the new row selection.

selectedObject
The object associated with the selected row.

Discussion

This method allows the delegate to handle the selection of a cell. For example, pushing a detail view for the selected item onto the navigation controller. The object associated with the cell is provided to allow the delegate to determine which cell was selected and to pull any context from the object. The index path is specifically not provided as the table may be displaying a search result which may have an index path different from what the delegate might expect.


bamEasyTable:didDeselectObject:

Tells the delegate that the specified row is now selected.

- (void)bamEasyTable:(BAMEasyTable *)easyTable didDeselectObject:(id)deselectedObject

easyTable
A BAMEasyTable object informing the delegate about the new row deselection.

selectedObject
The object associated with the selected row.

Discussion

This method allows the delegate to handle the deselection of a cell. For example, if custom cells are being provided by the delegate, a check mark could be deselected. The object associated with the cell is provided to allow the delegate to determine which cell was selected and to pull any context from the object. The index path is specifically not provided as the table may be displaying a search result which may have an index path different from what the delegate might expect.


bamEasyTable:cellForCustomization:withObject:

Provides the delegate with an opportunity to customize a cell before it is added to the table.

- (void)bamEasyTable:(BAMEasyTable *)easyTable cellForCustomization:(UITableViewCell *)cell withObject:(id)currentObject

easyTable
A BAMEasyTable object providing the delegate with a cell for customization.

cell
The table cell view that will be added to the table.

selectedObject
The object associated with the selected row.

Discussion

Allows the delegate to customize the cell that will be used in the table. For example, the delegate may wish to set the accessory type or the selection style for the cell. It is incredibly important that this delegate be lightweight to ensure speedy rendering of the table as it scrolls. The object associated with the cell is provided to allow the delegate to pull any necessary context from the object. The index path is specifically not provided as the table may be displaying a search result which may have an index path different from what the delegate might expect.


bamEasyTable:cellForObject:

Asks the delegate for a cell to insert into the table view.

- (UITableViewCell *)bamEasyTable:(BAMEasyTable *)easyTable cellForObject:(id)currentObject

easyTable
A BAMEasyTable object requesting the table view cell.

selectedObject
The object associated with the selected row.

Return Value

An object inheriting from UITableViewCell that the table view can use for the specified row. If nil is returned, the default cell for the row will be generated.

Discussion

This works the same as the UITableViewDataSource tableView:cellForRowAtIndexPath: delegate method with the exception that a nil value is valid as a return value. When a nil value is returned, the instance of BAMEasyTable will create a default cell for the row. This allows the delegate to only customize some cells while using the default for others.

The returned UITableViewCell object is frequently one that the application reuses for performance reasons. You should fetch a previously created cell object that is marked for reuse by sending a dequeueReusableCellWithIdentifier: message to tableView. The identifier for a reusable cell object is assigned when the delegate initializes the cell object by calling the initWithStyle:reuseIdentifier: method of UITableViewCell.

The object associated with the cell is provided to allow the delegate to pull any necessary context from the object. The index path is specifically not provided as the table may be displaying a search result which may have an index path different from what the delegate might expect.


bamEasyTable:heightForCellWithObject:

Asks the delegate for the height to use for a row with a specified object.

- (CGFloat)bamEasyTable:(BAMEasyTable *)easyTable heightForCellWithObject:(id)selectedObject

easyTable
A BAMEasyTable object requesting the row height.

selectedObject
The object associated with the selected row.

Return Value

A floating-point value that specifies the height (in points) that row should be.

Discussion

The method allows the delegate to specify rows with varying heights. If this method is implemented, the value it returns overrides the value specified for the rowHeight property of UITableView for the given row.

There are performance implications to using tableView:heightForRowAtIndexPath: instead of the rowHeight property. Every time a table view is displayed, it calls tableView:heightForRowAtIndexPath: on the delegate for each of its rows, which can result in a significant performance problem with table views having a large number of rows (approximately 1000 or more).

Important: Due to an underlying implementation detail, you should not return values greater than 2009.


bamEasyTable:titleForDeleteConfirmationButtonForRowWithObject:

Changes the default title of the delete-confirmation button.

- (NSString *)bamEasyTable:(BAMEasyTable *)easyTable titleForDeleteConfirmationButtonForRowWithObject:(id)selectedObject

easyTable
A BAMEasyTable object requesting the title.

selectedObject
The object associated with the selected row.

Return Value

A localized string to used as the title of the delete-confirmation button.

Discussion

By default, the delete-confirmation button, which appears on the right side of the cell, has the title of “Delete”. The table view displays this button when the user attempts to delete a row, either by swiping the row or tapping the red minus icon in editing mode. You can implement this method to return an alternative title, which should be localized.


bamEasyTableAddButtonPressed:

Tells the delegate that the user tapped add button.

- (void)bamEasyTableAddButtonPressed:(BAMEasyTable *)easyTable

easyTable
A BAMEasyTable object informing the delegate about the tapped add button.

Discussion

The delegate usually responds to the tap on the add button by displaying a new view for entering a record to later be inserted into the table.


bamEasyTable:accessoryButtonTappedForRowWithObject:

Tells the delegate that the user tapped the accessory (disclosure) view associated within a row associated with the selected object.

- (void)bamEasyTable:(BAMEasyTable *)easyTable accessoryButtonTappedForRowWithObject:(id)selectedObject

easyTable
A BAMEasyTable object informing the delegate about the tapped accessory button.

selectedObject
The object associated with the selected row.

Discussion

The delegate usually responds to the tap on the disclosure button (the accessory view) by displaying a new view related to the selected row. This method is not called when an accessory view is set for the row.


bamEasyTable:didRemoveItemAtIndexPath:

Tells the delegate that a row had been removed from the table.

- (void)bamEasyTable:(BAMEasyTable *)easyTable didRemoveItemAtIndexPath:(NSIndexPath *)indexPath

easyTable
A BAMEasyTable object informing the delegate about the new row removal.

indexPath
An index path locating the row in tableView.

Discussion

Once a user has selected “delete” from a row in the table, it will be removed from the table. the delegate is notified of the removal so that it can update its data accordingly. This will not be called when the table is displaying search results.


bamEasyTable:movedRowFromIndexPath:toIndexPath:

Tells the delegate a row at a specific location in the table view was moved to another location.

- (void)bamEasyTable:(BAMEasyTable *)easyTable movedRowFromIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath

easyTable
A BAMEasyTable object informing the delegate about the new row relocation.

fromIndexPath
An index path locating the row to be moved in tableView.

toIndexPath
An index path locating the row in tableView that is the destination of the move.

Discussion

The BAMEasyTable object sends this message to the delegate when the user uses the reorder control in a row to move it to a new location. This will not be called when the table is displaying search results.


bamEasyTable:canEditRowAtIndexPath:

Asks the data source to verify that the given row is editable.

- (BOOL)bamEasyTable:(BAMEasyTable *)easyTable canEditRowAtIndexPath:(NSIndexPath *)indexPath

easyTable
A BAMEasyTable object asking if the row is editable.

indexPath
An index path locating the row in tableView.

Return Value

YES if the row indicated by indexPath is editable; otherwise, NO.

Discussion

The method permits the delegate to exclude individual rows from being treated as editable. Editable rows display the insertion or deletion control in their cells. If this method is not implemented, the value set for BAMEasyTable’s allowRemoving property will determine whether the row is editable. Rows that are not editable ignore the editingStyle property of a UITableViewCell object and do no indentation for the deletion or insertion control. Rows that are editable, but that do not want to have an insertion or remove control shown. This will not be called when the table is displaying search results.


bamEasyTable:canMoveRowAtIndexPath:

Asks the delegate whether a given row can be moved to another location in the table view.

- (BOOL)bamEasyTable:(BAMEasyTable *)easyTable canMoveRowAtIndexPath:(NSIndexPath *)indexPath

easyTable
A BAMEasyTable object asking if the row can be moved.

indexPath
An index path locating the row in tableView.

Return Value

YES if the row indicated by indexPath can be moved; otherwise, NO.

Discussion

This method allows the delegate to specify that the reordering control for a the specified row not be shown. By default, the reordering control is shown if BAMEasyTable’s allowMoving property is set to YES. This will not be called when the table is displaying search results.


bamEasyTable:canRemoveSection:

Asks the delegate whether a given section can be removed.

- (BOOL)bamEasyTable:(BAMEasyTable *)easyTable canRemoveSection:(NSUInteger)section

easyTable
A BAMEasyTable object asking if the section can be removed.

section
An index number identifying a section in tableView.

Return Value

YES if the section indicated by section can be removed; otherwise, NO.

Discussion

The method permits the delegate to exclude individual sections from being removed. If this method is not implemented, the section will be removed when it becomes empty. This will not be called when the table is displaying search results.


bamEasyTable:didRemoveSection:

Tells the delegate that the specified section has been removed.

- (void)bamEasyTable:(BAMEasyTable *)easyTable didRemoveSection:(NSUInteger)section

easyTable
A BAMEasyTable object informing the delegate that a section has been removed.

section
An index number identifying a section in tableView.

Discussion

Once a user has deleted a row from the table, if the section becomes empty BAMEasyTable will automatically remove the section from the table unless the delegate implements bamEasyTable:canRemoveSection: and returns NO. The delegate is being notified of the removal so that it can update its data accordingly. This will not be called when the table is displaying search results.


bamEasyTable:heightForHeaderInSection:

Asks the delegate for the height to use for the header of a specified section.

- (CGFloat)bamEasyTable:(BAMEasyTable *)easyTable heightForHeaderInSection:(NSInteger)section

easyTable
A BAMEasyTable object requesting the header height.

section
An index number identifying a section in tableView.

Return Value

A floating-point value that specifies the height (in points) that header should be.

Discussion

This method allows the delegate to specify section headers with varying heights.


bamEasyTable:viewForHeaderInSection:withTitle:

Asks the delegate for a view object to display in the header of the specified section of the table view.

- (UIView *)bamEasyTable:(BAMEasyTable *)easyTable viewForHeaderInSection:(NSInteger)section withTitle:(NSString *)title

easyTable
A BAMEasyTable object requesting the header view.

section
An index number identifying a section in tableView.

title
A string value containing the current title for the header.

Return Value

A view object to be displayed in the header of section.

Discussion

The returned object, for example, can be a UILabel or UIImageView object. The table view automatically adjusts the height of the section header to accommodate the returned view object. This method only works correctly when bamEasyTable:heightForHeaderInSection: is also implemented.


bamEasyTable:heightForFooterInSection:

Asks the delegate for the height to use for the footer of a specified section.

- (CGFloat)bamEasyTable:(BAMEasyTable *)easyTable heightForFooterInSection:(NSInteger)section

easyTable
A BAMEasyTable object requesting the footer height.

section
An index number identifying a section in tableView.

Return Value

A floating-point value that specifies the height (in points) that header should be.

Discussion

This method allows the delegate to specify section headers with varying heights.


bamEasyTable:viewForFooterInSection:withTitle:

Asks the delegate for a view object to display in the footer of the specified section of the table view.

- (UIView *)bamEasyTable:(BAMEasyTable *)easyTable viewForFooterInSection:(NSInteger)section withTitle:(NSString *)title

easyTable
A BAMEasyTable object requesting the header view.

section
An index number identifying a section in tableView.

title
A string value containing the current title for the footer.

Return Value

A view object to be displayed in the footer of section.

Discussion

The returned object, for example, can be a UILabel or UIImageView object. The table view automatically adjusts the height of the section footer to accommodate the returned view object. This method only works correctly when bamEasyTable:heightForFooterInSection: is also implemented.

2 Comments Posted in BAMEasyTable, iOS Programming | Tagged , , |

BAMEasyTable

BAM Easy Table

Making UITableView Easier

BAMEasyTable is a subclass of UITableViewController which handles all of the complexity of creating a searchable table with insert and delete capabilities. After writing custom implementations of tables over and over again, it occurred to me that I was investing way too much time writing the same code repeatedly. Which I hate doing!

So I took some time to write a generic class to handle the mechanics of the UITableView while leaving me free to only worry about my data. It seems to me, that over 90% of the time, I just want to display data that can be represented by an array or an array of arrays. So BAMEasyTable class handles that model.

Simple Configuration

BAMEasyTable was designed to be very easy to implement while still providing the more advanced features you need. To that end, to use BAMEasyTable, Just add it to any view and you can start using it.

Powerful Delegate Functions

Once you’ve created your BAMEasyTable, you can begin responding to the powerful delegate methods it provides. Obviously, you can be informed of when the user selects an item but there is also you can insert and remove rows, as well as, change the appearance of the cells, search bar, and table. The BAMEasyTable Delegate post gives you more information about the delegate.

Use It for Free

As usual, I’m releasing BAMEasyTable publicly under the permissive Simplified BSD License (FreeBSD) to allow reuse by anyone who wants to use it. You can modify the code in any way that suits your project. I would appreciate a credit and a link back to this site if you use it but it certainly is not required and I would enjoy hearing from anyone who uses this in their projects.

How to Use BAMEasyTable

The BAMEasyTable Demo post gives detailed instructions for adding BAMEasyTable to your project and includes a downloadable demo project. Additional details about the delegate methods can be found in the BAMEasyTable Delegate post and a recommend that you become familiar with them to get the maximum benefit from BAMEasyTable.

Hopefully, this will save everyone countless hours of writing the same code over and over. If you find this useful please let me know below.

Download from GitHub

 
 
 
 

Leave a comment Posted in BAMEasyTable, iOS Programming | Tagged , , |

Simplifying Facebook iOS SDK

The Problem with Facebook iOS SDK

Facebook+Singleton
Now that the Facebook iOS SDK has been changed to use Graph API, many people are trying to figure out just how to use it properly. In my opinion, however, there are problems with the SDK’s architecture that make it likely to be used poorly. For example, Facebook’s documentation recommends instantiating the Facebook class and authenticating from within your AppDelegate. This is suboptimal for a several of reasons.

First, in many apps that allow Facebook integration, the user may never actually use the Facebook features. Thus, loading the class and authenticating the user is something that should only be done when the user has decided to use a Facebook feature. In other words, we should be lazy loading the Facebook class but their documentation suggests otherwise.

Secondly, the log in method chosen by Facebook (fast-switching the user to the Facebook app or Safari to log in) makes a very poor first impression for your app. Who wants their app to look like it crashed then loaded some Web page the very first time it’s launched?

Lastly, this just begs the programmer to overload the AppDelegate with all kinds of crap or instantiating multiple instances of the Facebook class throughout their app. Facebook’s documentation leaves it to your imagination whether

[facebook requestWithGraphPath:@"me" andDelegate:self];

really means a call to the Facebook instance in your AppDelegate:

Facebook *facebook = [(MyAppDelegate *)[[UIApplication sharedApplication] delegate] facebook];
[facebook requestWithGraphPath:@"me" andDelegate:self];

or whether you should be instantiating a new instance of the Facebook class:

Facebook *facebook = [[Facebook alloc] initWithAppId:@"YOUR_APP_ID"];
·
·
·
[facebook requestWithGraphPath:@"me" andDelegate:self];

Neither of which is ideal.

A Better Way

By all rights, the Facebook class should have been implemented as a singleton method. Which simply means there can only be one instance of the class within your application. So I’ve created a category that will make it a singleton method. Likewise, using the class can be simplified greatly by having it maintain it’s own state information rather than cluttering the AppDelegate or requiring the programmer to repeatedly check their NSUserDefaults every time they instantiate the object. So I’ve moved that functionality to the Facebook+Singleton category also.

Using Facebook+Singleton

To use my category, you should get the Facebook iOS SDK from GitHub and add it to your project as usual (See Facebook’s documentation for more details.) Then you’ll need to drag my category files, Facebook+Singleton.h and Facebook+Singleton.m, to your project.

In your AppDelegate, you will want to import Facebook+Singleton.h instead of FBConnect.h, like so:

#import "Facebook+Singleton.h"

Then you will only add the following method to your AppDelegate:

1
2
3
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation {
    return [[Facebook shared] handleOpenURL:url];
}

Note that I’m not using the application:handleOpenURL: as in Facebook’s documentation because that method has been deprecated. This means you should only use that method if your application is targeting an iOS version prior to version 4.2.

Okay, a quick word about why we’re doing this. Didn’t I just say that we shouldn’t clutter the AppDelegate with a bunch of stuff? So why do we need these there?

Since Facebook’s preferred authentication methodology requires leaving your app and launching another app, there needs to be a way for your app to handle the return trip. The application:openURL:sourceApplication:annotation: is that method. So we must have this method in our AppDelegate to handle the authentication when it occurs. However, by using Facebook+Singleton in this way, your app will never instantiate the Facebook class unless it is handling an open URL. Which should normally only occur the very first time a user needs to authenticate with Facebook and never prior to nor again after the first authentication.

That’s all we need in the AppDelegate.

Configuring Your App ID

Next, you’ll need to configure your app id. You’ll need to put it in your info.plist file just like in Facebook’s documentation. But instead of also adding your app id to the AppDelegate, you’ll need to set it in the init method of the Facebook+Singleton.m file instead. Here you’ll place the same app id you would have put in the AppDelegate in the spot where it says YOUR_APP_ID as below:

1
2
3
4
5
6
7
- (id)init {
    if ((self = [super init])) {
        [self initWithAppId:@"YOUR_APP_ID"];
        [self authorize];
    }
    return self;
}

Calling the Graph API

Now, to make calls to the Graph API, you will only need to do the following from any class in your app:

//get information about the currently logged in user
[[Facebook shared] requestWithGraphPath:@"me" andDelegate:self];

//get the logged-in user's friends
[[Facebook shared] requestWithGraphPath:@"me/friends" andDelegate:self];    

//call a legacy REST API
NSMutableDictionary* params = [NSMutableDictionary
    dictionaryWithObjectsAndKeys: @"4", @"uids", @"name", @"fields", nil];

[[Facebook shared] requestWithMethodName: @"users.getInfo"
    andParams: params andHttpMethod: @"GET" andDelegate: self];

Then implement the FBRequestDelegate as usual, specifically the request:didLoad: method for successful requests and request:didFailWithError: method for errors.

Displaying Platform Dialogs

Likewise, with dialogs you’ll need to implement the FBDialogDelegate and call them like:

[[Facebook shared] dialog:@"feed" andDelegate:self];

But Wait, We Didn’t Authorize

Ahhh, this is where the magic starts to appear. With Facebook+Singleton, when the class instantiates, it also automatically authenticates if necessary. However, if you only authenticate in your AppDelegate as Facebook seems to suggest, you are expecting your user to obediently allow your app to access their Facebook account as soon as they launch the app. This is woefully optimistic.

The more non-Facebook functionality your app has, the less likely it is that the user has already allowed it to access Facebook. The last thing you want is for your user to get a “You just defeated the Horrible Dragon. Would you like to share this on Facebook.” message only to discover they can’t share because they didn’t log into Facebook when they first launched the app.

This means you need to ensure that your app can authenticate every time a user decides to begin using a Facebook feature. This is when the existing Facebook iOS SDK becomes a pain because on every page you’ll need to check whether the NSUserDefaults exist, whether a valid session exists, authenticate if not, implement the FBSessionDelegate, and update the NSUserDefaults. Oh yeah, then you should make the call you intended to make. This gets tedious real quick.

The Facebook+Singleton category encapsulates this and does it automatically. The parts that still need to be handled by your class can be handled with NSNotifications.

NSNotifications? What are those?

Since I’ve encapsulated the session states in the Facebook+Singleton category, it is consuming the FBSessionDelegate. It should be the only place that implements the session delegate in your app. You should never set the _sessionDelegate nor call any of the Facebook class methods that implement the id<FBSessionDelegate>, (basically the authorize:delegate:, authorize:delegate:localAppId: and logout: methods.)

Instead, the Facebook class will let your class know about significant session events using NSNotifications. To subscribe to a notification, you should place something like the following in your init or initWithNib: method as appropriate:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(requestFacebookData) name:@"FBDidLogin" object:nil];

In the example above, you have subscribed your class to receive the “FBDidLogin” notification as defined in the Facebook+Singleton category. The “FBDidLogin” gets called once the application has successfully authenticated with Facebook. The selector is the name of any method in your class that you would like to have called when the notification occurs. In this case it is calling a method called requestFacebookData.

You should also unsubscribe any notifications you use in your viewWillDisappear: method like this:

[[NSNotificationCenter defaultCenter] removeObserver:self name:@"FBDidLogin" object:nil];

So, for example, if you have a UITableView that should display a list of your user’s friends, the code could look something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#import "Facebook+Singleton.h"

- (id)init {
    if ((self = [super init])) {
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(requestFacebookData) name:@"FBDidLogin" object:nil];
    }
    return self;
}

- (void)viewDidLoad {
        [self requestFacebookData];
}

- (void)requestFacebookData {
        [[Facebook shared] requestWithGraphPath:@"me/friends" andDelegate:self];
}

- (void)viewWillDisappear:(BOOL)animated {
        [[NSNotificationCenter defaultCenter] removeObserver:self name:@"FBDidLogin" object:nil];
}

Of course, you’ll still need to implement the FBRequestDelegate as usual, specifically the request:didLoad: method for successful requests and request:didFailWithError: method for errors. As you can see, this is a much simpler way to implement the Facebook class than is originally provided by the Facebook iOS SDK.

What Notifications Are Available?

The notifications posted by the Facebook+Singleton category are:

  • FBDidLogin—a successful authentication has occurred.
  • FBDidLogout—the user is successfully logged out.
  • FBDidNotLogin—a login is unsuccessful and also calls one of:
    • FBLoginCancelled—when a user cancels.
    • FBLoginFailed—failed for any other reason.

Another Word about Authentications

The method the Facebook iOS SDK uses for authentication is somewhat annoying. It exits your app and loads either the Facebook app or Safari to handle the authentication. The reason Facebook uses this method is because any app running on the iPhone is sandboxed to keep apps from maliciously using the UIWebView to use your credentials on other sites. However, this also means the cookies used for tracking sessions on Facebook are not available to your app. To get around this, Facebook prefers to exit your app and launch into another app which may likely already have an active Facebook session to keep the user from needing to type in their username and password.

However, the authorization process is infrequent. It only needs to be done once from each app (unless the app is deleted and reinstalled or otherwise logged out) so it is not a huge burden to require the user to type in their credentials. When handled from within your app, though, it looks much cleaner to the user than the fast app switching employed in the Facebook iOS SDK. In my opinion, Facebook’s preferred authentication makes your app look clumsy and amateurish. For that reason, I have also enabled you to change the login behavior when using the Facebook+Singleton category.

If you look within the Facebook+Singleton.m file, you will see that there is an authorize method which calls one of the following methods:

  • authorize:localAppId: The standard authentication used by Facebook which exits your app to login via the Facebook app or Safari.
  • authorizeInApp:localAppId: Pops up a standard FBDialog, which is simply a UIWebView, and never exits your app. If you use this method the user must type in an email/phone number and password when they first authenticate. Additionally, you will not need any of the previously mentioned code in your AppDelegate.
  • authorizeWithFacebookApp:localAppId: – Only leaves your app if the user has the Facebook app installed.

You can choose to use any of these methods in your app by uncommenting the line and commenting the others. Likewise, if you need to specify permissions or add a local app id you should do so here. If you don’t know what that means, just leave them set to nil.

Download Facebook+Singleton and Try It Out

I would like to thank you for taking a look at the Facebook+Singleton category and I hope you find it useful for your project. You can download them below.

As time permits, I will be releasing other Facebook related classes that build upon the Facebook+Singleton category.

Facebook+Singleton.h

Facebook+Singleton.m

5 Comments Posted in Facebook iOS SDK, iOS Programming | Tagged , , , |

BAMSettings Demo

BAM Settings Demo iPhone

What Is BAMSettings?

BAMSettings is a generic handler for presenting and changing settings from within your iPhone app. By adding two files to your app and pushing BAMSettings onto your navigation controller, you will have the full functionality of Apple’s Settings App from within your app. This post explains how to use BAMSettings. For additional information please see my post introducing BAMSettings.

Using BAMSettings

BAMSettings was designed to be very easy to implement in your app. To use BAMSettings in your app, simply copy BAMSettings.h and BAMSettings.m into your project and include BAMSettings.h in your view controller like this:

#include "BAMSettings.h"

Push BAMSettings onto Your Navigation Controller

Initialize BAMSettings with any title you like and a property list from your settings bundle (usually Root.plist) then push BAMSettings onto your navigation controller. Of course, you will need to determine the best usage for your own app but it is common to do this after a user presses a button like this:

1
2
3
4
5
- (IBAction)buttonPressed:(id)sender {
    BAMSettings *settings = [[BAMSettings alloc] initWithTitle:@"Settings" propertyListNamed:@"Root"];
    [self.navigationController pushViewController:settings animated:YES];
    [settings release];
}

That’s All!

That’s all you need to do. From this point, once the user clicks the settings button BAMSettings will generate the settings tables based on the property lists in your settings bundle and automatically save any changes made by the user.

Optional Delegates

There may be times when you want to know when was changed or the user has navigated away from BAMSettings. To make this easier, there are a few optional delegates that you may use. Of course, you’ll need to set the delegate by changing the above to:

1
2
3
4
5
6
- (IBAction)buttonPressed:(id)sender {
    BAMSettings *settings = [[BAMSettings alloc] initWithTitle:@"Settings" propertyListNamed:@"Root"];
    settings.delegate = self;
    [self.navigationController pushViewController:settings animated:YES];
    [settings release];
}

Then you will be able to implement the following delegate methods in your app:

- (void)settingsDidChangeValueForKey:(NSString *)key;

Each time a user changes a value this will be called to notify your code with the changed setting’s key.

- (void)settingsExitedWithChangedValues:(BOOL)didChange;

This will wait until the user exits BAMSettings to notify your application whether any changes occurred.

- (void)settingsNavigatedAwayFromPane:(NSString *)propertyListName
                    withChangedValues:(BOOL)didChange;

If you use child panes, this will notify your application whether any changes occurred when the user navigates away from the named pane.

BAMSettings Demo App

To make this as easy to understand as possible, I have created a demo app that shows BAMSettings in action. Even if you understood everything above, you may still find the example settings bundle to be informative because I tried to add several unusual cases to put BAMSettings through its paces.

Download from GitHub

 
 
 
 

3 Comments Posted in BAMSettings, iOS Programming | Tagged , , , |

BAMSettings

BAM Settings iPhone

Easy In App Settings

BAMSettings is a generic handler for presenting and changing settings from within your iPhone app. By adding two files to your app and pushing BAMSettings onto your navigation controller, you will have the full functionality of Apple’s Settings App from within your own app.

Why Use BAMSettings?

Okay, so you’ve carefully crafted a settings bundle for your iPhone app and you would like to give your users the ability to change settings from within your app, too. After writing several custom settings pages it has occurred to me that it would be useful to have a generic handler for this purpose. Since this is such a common occurrence, I’ve decided to release the code publicly so you use it in your apps for free.

Identical to iPhone Settings

Your existing settings bundle will be used to present an interface which is identical to the existing iPhone settings. It is fully localized and will use your strings files to present the settings in whatever language the user has selected on their phone. Your multi value fields, text fields, toggle switches, sliders and groups will be automatically populated in the same familiar way that your users have become accustomed to. In addition to the usual setting types, you can also use group footers, child panes, and title values which are less common and not in the default Xcode dropdown menus.

Even Better than iPhone Settings

While writing this, I couldn’t help but add a few small improvements: The keyboard can be dismissed by pressing the return key while there is no way to dismiss a keyboard on the iPhone settings app, BAMSettings can rotate to any orientation while the iPhone settings app only supports the portrait orientation, and in an effort to ease the debugging of your settings bundle, BAMSettings will display a table cell informing you if you use a setting type that is unknown rather than simply ignoring it. If you run across a setting type that Apple’s settings app handles but is unknown to BAMSettings, please let me know so I can update the code.

Use It for Free

I’m releasing BAMSettings publicly under the permissive Simplified BSD License (FreeBSD) to allow reuse by anyone who wants to use it. You can modify the code in any way that suits your project whether simply changing colors and fonts or even adding custom settings types. It was designed to be a great jumping off point for any in-app settings project. I would appreciate a credit and a link back to this site if you use it but it certainly is not required and I would enjoy hearing from anyone who uses this in their projects.

How to Use BAMSettings

The BAMSettings Demo post gives detailed instructions for adding BAMSettings to your project and includes a downloadable demo project.

I have chosen to leave BAMSettings sparsely commented. It is my belief that comments simply dilute the code (by decreasing the amount of actual code onscreen) when it contains thoughtful naming and is viewed by an experienced programmer. However, if others would find it useful, I would be willing to write a blog entry that explains precisely how the code within BAMSettings works.

Download from GitHub

 
 
 
 

Leave a comment Posted in BAMSettings, iOS Programming | Tagged , , |

Check Split Icon Permutations

Sometimes being a one-man company has its limitations. Designing a meaningful icon is just such a time. After many hours of designing I become to close to the designs or fall in love with elements that may not be meaningful to everyone. At times like this I appreciate feed back from others. So, I’m posting this to elicit feedback from you.

Check Split is an iPhone app I’m currently developing and I’m torn between a practical icon which shows the check with different payment methods or a symbolic interpretation which shows the check with a split in it. Additionally, I could go with a hybrid of the two concepts but I’m not certain what degree of hybrid best represents the app. Below is a grid of several of my current concepts at a size that represents the actual detail available on the menu of the iPhone 4 and 4th generation iPod Touch.

Check split icon grid

Of course, the design process for iPhone icons is more complex because the icon can be displayed at various other sizes, such as settings on the iPhone or completely different sizes for the iPad icons. But there is also a large size that is used in iTunes or Apple’s App Store. Often, a design which looks good in the smaller size is too simple and boring at the larger size while a design that looks great at a larger size becomes too muddy when reduced to a smaller size. As an example, look at a few of the icons at a larger size below.

Check Split Icon B3 Check Split Icon D4Check Split Icon F2

So, dear reader, please help me by letting me know which icons you like best and which design elements you feel best represent the app.

4 Comments Posted in Check Split, Graphic Design | Tagged , , , |
  • GitHub Projects

  • T-Shirts

Privacy Policy    Terms of Service