Five freaking awesome FaceTime hacks — and a few handy tips

We’ve all been using FaceTime like crazy here at TUAW central — it’s really great to be able to conference with friends in real time without having to arrange things in advance. Nearly all of us have been video-conferencing-ready for years. But with the iPhone 4, there’s no more “Do you have iChat set up?” (or Skype) or “Can I call you now?” time-wasting prologues.

Instead, we can just call. Knowing that your friends have iPhone 4s makes video calling much easier. You don’t have to call or text to arrange the call, you just place it and you’re immediately good to go. We may have already had webcam equipment on hand but it’s only with the iPhone 4 that, at least here at TUAW, we’re actually using video calling.

With that in mind, we’ve been seeing how far we can push the technology. We’ve put together a list of the coolest techniques that we’ve actually tried out and tested and can confirm as working. In no particular order, here they are.

TUAWFive freaking awesome FaceTime hacks — and a few handy tips originally appeared on The Unofficial Apple Weblog (TUAW) on Fri, 06 Aug 2010 20:00:00 EST. Please see our terms for use of feeds.

Read | Permalink | Email this | Comments

Picasa find: Finder icon gets a new job on Canon digicam

That poor Finder icon. The Rodney Dangerfield of Apple products, he gets no respect. Heck, he’s nowhere to be found on the iPhone or the iPad, and he just sits down in the Dock of the Mac, getting little or no work from anybody.

It’s apparent that Mr. Finder has had his fill of Apple and the Mac, as he’s now finding work elsewhere. TUAW reader Aleksander found Mr. Finder hanging out on the back of a new Canon A3000IS digital camera, where he’s apparently used to enable a face detection function. Sure, the FInder icon has tried to cover up his face by putting on a little makeup to get rid of that two-tone blue tan, and it looks like he may have also gotten a little rhinoplasty, but it’s him.

We’re wondering just how widespread Mr. Finder’s disloyalty to Apple really is. If you happen to find him anywhere else, snap a photo of him, send it to Flickr, and tag it with TUAW so the world will know.

TUAWPicasa find: Finder icon gets a new job on Canon digicam originally appeared on The Unofficial Apple Weblog (TUAW) on Sat, 07 Aug 2010 08:00:00 EST. Please see our terms for use of feeds.

Read | Permalink | Email this | Comments

Why Apple’s patents might show screens from third-party apps

This is a weird one. FutureTap was surprised to recognize one of their app’s screens in a strange place: a recent Apple patent outlining how a possible travel application could work. The company wasn’t quite sure what to do next — as you can see above, it’s a straightforward copy of the Where To? screen. Without any contact from Apple on the issue, FutureTap was puzzled — the company calls Apple its “primary business partner.” Having your app show up in an Apple patent filing

Not that Apple is above cherry-picking UI and functionality from third-party apps — the iBooks interface was more or less borrowed whole from apps like Delicious Library and Classics, and further back there was the homage of Karelia Software’s Watson reinterpreted as Apple’s revision to Sherlock. In those cases, both developers just sort of shrugged, felt flattered, and let it go. That’s not the situation here — these screenshots probably aren’t illustrations of a product Apple wants to make, they’re explanations of how the patent would work.

As Engadget’s Nilay Patel clarifies for us (so nice to have an attorney around), not only is this merely a patent application (which could possibly still be denied), but Apple isn’t trying to lay claim to the actual screenshot; rather the company is using an example application which illustrates the to-be-patented functionality (in the case of Where To?, the ability of an iPhone to auto-detect when a user has been traveling). As Nilay puts it:

the only operative parts of a patent are the claims — not the drawings, and not the description, which are technically known as the “specification.” (We’ve now repeated this basic axiom of patent interpretation so many times we’re considering making T-shirts.) The only reason the drawings and description are there is to explain the claimed invention in sufficient detail so that someone else can make it. Remember, patents are a trade: in order to get protection, you have to give up the full details of how your invention works. (The other option is to keep your invention a trade secret, but then you can’t prevent anyone else from figuring it out and using it if it gets out.) Bottom line? If it’s not in the claims, it’s not in the patent.

We’ll have to see what FutureTap does — the company might request that Apple remove the Where To? screenshot from the patent application, but at this point it’s not clear that Apple wants to lay claim to that particular app. What is clear is that Apple could have avoided a lot of hassle and furor if it had simply sent FutureTap an email asking to use a screenshot in a patent filing. In the meantime, FutureTap execs are huddled with their lawyers, trying to make sure they understand what’s going on.

TUAWWhy Apple’s patents might show screens from third-party apps originally appeared on The Unofficial Apple Weblog (TUAW) on Sat, 07 Aug 2010 11:00:00 EST. Please see our terms for use of feeds.

Read | Permalink | Email this | Comments

TUAW Review: Filemaker Go for iPad

Filemaker has brought its flagship product to a whole new level of portability with Filemaker Go for iPad (US$39.99) and iPhone/iPod touch ($19.99). Years ago I used Filemaker Mobile with a Palm, and this is a far cry from those early days. Here’s what I found while testing Filemaker Go on the iPad.

Design

When FileMaker Go is first launched, you’re presented with two columns: stored files on the left and remote files and hosts on the right. Configuring a remote host is easy and will be immediately familiar to anyone who’s done it before with the desktop version of FileMaker Pro. In the upper right-hand corner you’ll find two familiar icons: add a host and search for a host.

If you choose to add a host, a slip will flip into view (nice bit of eye candy there) and ask for its IP address or domain name. Optionally, you can enter the host’s name, which is helpful if you have multiple servers to keep track of (“Corporate Office” and “School Building,” for example). Note that FileMaker Go connects to databases hosted by FileMaker Server/Server Advanced 11 and 10 and FileMaker Pro/Pro Advanced 11 and 10.

TUAWTUAW Review: Filemaker Go for iPad originally appeared on The Unofficial Apple Weblog (TUAW) on Fri, 06 Aug 2010 16:00:00 EST. Please see our terms for use of feeds.

Read | Permalink | Email this | Comments

A Delta Airlines app is on the way

delta iphone iconRather recently, American Airlines released an iPhone app that offers quite a few nice features for the AA traveler, including flight status, boarding pass access and … Soduku. With the exception of that last feature, travel app Kayak offers many of these features already, though it’s not stopping other airlines from following suit.

Just the other day, a Delta executive mentioned in a short interview that a Delta iPhone app is on the horizon. It’s not clear yet what kinds of services and features the app will provide, though it’s a safe bet the American Airlines app will give you a decent guess.

What kinds of services and features would you want to see in an airline-dedicated iPhone app that’s not already present in current offerings?

TUAWA Delta Airlines app is on the way originally appeared on The Unofficial Apple Weblog (TUAW) on Fri, 06 Aug 2010 17:00:00 EST. Please see our terms for use of feeds.

Read | Permalink | Email this | Comments

iPad app store gains genius section

ipad genius app storeSometime early this morning, Apple quietly unleashed the genius section of the iPad app store, something that’s been in the iPhone app store for quite some time. It’s interesting how Apple is always “quietly” releasing new things, while making a big to-do about others. What’s also interesting is how a new feature in the app store can be made without a software update.

Not only does the new genius section of the iPad app store provide the same type of recommendations as with the iPhone, there’s a new tab titled “iPad Upgrades,” which shows you all of the iPad (aka, “HD”) versions of the iPhone apps you already have. This is a great new feature for both consumers and app developers because, previously, there was no easy way to know.

Thanks for everyone who sent this in!

TUAWiPad app store gains genius section originally appeared on The Unofficial Apple Weblog (TUAW) on Fri, 06 Aug 2010 14:00:00 EST. Please see our terms for use of feeds.

Read | Permalink | Email this | Comments

Guy moving to New York wants a roomie who doesn’t have an iPhone

An ad posted to Craigslist yesterday is from an open-minded sort of guy who wants a roommate for an apartment in Soho. Sounds typical, right? Here’s the catch: He insists that prospective roommates not have an iPhone or an iPad.

The ad states that “I refuse to live with anyone that has sold their immortal soul to Steve Jobs.” He’s also looking for a guy with Starcraft II experience, and wants any potential live-ins to post a link to their battle.net profile and to prove to him that they don’t own an iPhone.

The New York Observer found the guy, Vince Thomas, and he said he currently lives with another fellow who is a Mac fan, and Vince says he “just can’t take it anymore.” Vince has a Droid phone from Motorola, and apparently he’d rather talk about that instead.

Vince has a point. Some of us love our iPhones. In social situations I don’t bring it up unless someone asks. I’m even feeling a bit guilty about the ‘Sent from my iPhone’ tag on my mail. If I write Vince I’ll be sure to change it to ‘Sent from my wonderful Droid, which is so much better than the iPhone I ditched.’

On the other hand, I’ve been around a few Droid owners, and let me tell you, that’s no picnic either. Good luck Vince. May you find a roommate just like you.

[Via The New York Observer]

TUAWGuy moving to New York wants a roomie who doesn’t have an iPhone originally appeared on The Unofficial Apple Weblog (TUAW) on Fri, 06 Aug 2010 15:00:00 EST. Please see our terms for use of feeds.

Read | Permalink | Email this | Comments

UITableView – Indexed table view

In this tutorial you will learn how to create a simple index for the table view. This index view shows up on the right side, just like the contacts application. Click on “Read more…” to learn more

Introduction
Displaying an index for the table view is another way for the users to get the data faster. This tutorial will show you how to do that. This is the fifth tutorial in the UITableView tutorial series and borrows its code from the previous one. This is how the app will look like

Creating an Index
The first thing we need to do is create an array, which will contain the index values to display. The values in the array has to be of type NSString. The Apple documentation says that the style of the table view should be set to UITableViewStylePlain. However, it does not throw an error when the style is set to UITableViewStyleGrouped. The array is returned in sectionIndexTitlesForTableView method and this is how the code looks like

//RootViewController.m
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {

if(searching)
return nil;

NSMutableArray *tempArray = [[NSMutableArray alloc] init];
[tempArray addObject:@"1"];
[tempArray addObject:@"2"];
[tempArray addObject:@"3"];
[tempArray addObject:@"4"];
[tempArray addObject:@"5"];
[tempArray addObject:@"6"];
[tempArray addObject:@"7"];
[tempArray addObject:@"8"];
[tempArray addObject:@"9"];
[tempArray addObject:@"10"];
[tempArray addObject:@"11"];
[tempArray addObject:@"12"];
[tempArray addObject:@"13"];
[tempArray addObject:@"14"];
[tempArray addObject:@"15"];
[tempArray addObject:@"16"];
[tempArray addObject:@"17"];
[tempArray addObject:@"18"];
[tempArray addObject:@"19"];
[tempArray addObject:@"20"];
[tempArray addObject:@"21"];
[tempArray addObject:@"22"];
[tempArray addObject:@"23"];
[tempArray addObject:@"24"];
[tempArray addObject:@"25"];
[tempArray addObject:@"26"];

return tempArray;
}

The above code creates an array and returns it. You can populate this array with alphabets from ‘A’ to ‘Z’. If the user is searching the table view, then the array is not returned. This way the index view is not shown.

Handling Events
When an item in the index is clicked, the section starting with the selected index item is displayed. The event that gets called is tableView:sectionForSectionIndexTitle:atIndex, which returns an index of the section to display. Since our data source only has two sections and 26 index values, the code looks like this

//RootViewController.m
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {

if(searching)
return -1;

return index % 2;
}

If the user is searching the table view, then we do not return the index of the section. The data source is changed a little to include some test objects, so the section selection is obvious. Since the index shows up very close to the accessory view, the accessory view is set to UITableViewCellAccessoryNone.

Conclusion
Just by implementing two methods, we have created an index for the table view. However, I think Apple has kept some methods private. Since we cannot display the magnifying glass like the contacts application. If you have an idea on how to get it working, please let us know.

Happy Programming,
iPhone SDK Articles


Attachments

Suggested Readings

UIPickerView – Creating a simple picker view

Apple provides a control knows as the UIPickerView, with which a user can select an item from a list. In this tutorial you will learn how to create a simple picker view. Click on “Read more…” to continueIntroduction
The picker view lets the user select an item from a list. In this tutorial, you will learn how easy it is to configure a picker view and respond to events. This is how the app will look like


Creating the project
Create a new project by selecting “Windows-Based Application”, I have named my project “PickerView”. I also added another view called “PickerView” and created a view controller for the view called “PickerViewController”. Add a UIPickerView to the view and create all the right connections. The “PickerViewController” implements two protocols and this is how the header file looks like

//RootViewController.h
#import <UIKit/UIKit.h>

@interface PickerViewController : UIViewController <UIPickerViewDataSource, UIPickerViewDelegate> {

IBOutlet UIPickerView *pickerView;
NSMutableArray *arrayColors;
}

@end

The connections of the picker view looks like this

This is how the UIPickerView is added to the subview of the window.

//RootViewController.m
- (void)applicationDidFinishLaunching:(UIApplication *)application {

pvController = [[PickerViewController alloc] initWithNibName:@"PickerView" bundle:[NSBundle mainBundle]];

[window addSubview:pvController.view];

// Override point for customization after application launch
[window makeKeyAndVisible];
}

Creating the data source
Lets create a data source, with all the colors in a rainbow. The array is declared in the header file and released in the dealloc method. It is populated in viewDidLoad method and this is how the code looks like

//PickerViewController.m
- (void)viewDidLoad {
[super viewDidLoad];

arrayColors = [[NSMutableArray alloc] init];
[arrayColors addObject:@"Red"];
[arrayColors addObject:@"Orange"];
[arrayColors addObject:@"Yellow"];
[arrayColors addObject:@"Green"];
[arrayColors addObject:@"Blue"];
[arrayColors addObject:@"Indigo"];
[arrayColors addObject:@"Violet"];
}

The picker view can be divided into components, we need to specify how many components to display. This is done in numberOfComponentsInPickerView method. This is how the source code looks like

//PickerViewController.m
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)thePickerView {

return 1;
}

Now that the picker view knows how many components it should expect, we need to specify how many rows it should display. This is done in pickerView:numberOfRowsInComponent. This is how the source code looks like

//PickerViewController.m
- (NSInteger)pickerView:(UIPickerView *)thePickerView numberOfRowsInComponent:(NSInteger)component {

return [arrayColors count];
}

The method pickerView:titleForRow:forComponent gets called n number of times, where n is the number returned in pickerView:numberOfRowsInComponent. This is how the source code looks like

//PickerViewController.m
- (NSString *)pickerView:(UIPickerView *)thePickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {

return [arrayColors objectAtIndex:row];
}

In the above code, we return the object at index using the row parameter. Here we ignored the component parameter, as we only have one component to display. If you have more then one component, then we return the row present in that given component. Run your application to see the values in the picker view.

Handling Events
The method pickerView:didSelectRow:inComponent is called when an item is selected in the picker view. In the example, I simply log the selected color and the row number. This is how the code looks like

//PickerViewController.m
- (void)pickerView:(UIPickerView *)thePickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component {

NSLog(@"Selected Color: %@. Index of selected color: %i", [arrayColors objectAtIndex:row], row);
}

Conclusion
Picker view is a perfect control to display a list of items, from which one single item can be selected. In this tutorial, we saw how easy it is to configure a picker view and respond to events. I hope this tutorial has helped you in some little way. Stay tuned for some more tutorials on the UIPickerView object.

Happy Programming,
iPhone SDK Articles


Attachments

Suggested Readings

UITableView – Adding subviews to a cell’s content view

Using the default UITableViewCell we can display a text or an image but sometimes it is not good enough. In this tutorial you will learn how to customize the UITableViewCell.

Introduction
Using the default UITableViewCell, we can display a text (using the text property) or an image (using the image property). However, we cannot display data in multiple columns or rows. This is the sixth tutorial in the UITableView tutorial series and borrows its source code from the previous one. This is how the final app will look like

We cannot achieve the display we want by setting the text property of the UITableViewCell. To solve this problem, we are going to add two labels to the content view of the cell. The labels are added in such a way that they show up one above the other by using coordinates. Apple documentation says we should add subviews to the cell’s content view, if you do not want to change the default behavior of the cell. This is the reason why we are not going to inherit from UITableViewCell.


Adding Sub Views
Instead of creating a cell by using initWithFrame:reuseIdentifier, we will create it from our own custom method. This method will be responsible for not only creating the cell but also the labels. The labels are created with specific locations in the cell using x and y coordinates. They are added to the content view of the cell as seen below

//RootViewController.h
#import <UIKit/UIKit.h>

@class OverlayViewController;

@interface RootViewController : UITableViewController {

NSMutableArray *listOfItems;
NSMutableArray *copyListOfItems;
IBOutlet UISearchBar *searchBar;
BOOL searching;
BOOL letUserSelectRow;

OverlayViewController *ovController;
}

- (UITableViewCell *) getCellContentView:(NSString *)cellIdentifier;
- (void) searchTableView;
- (void) doneSearching_Clicked:(id)sender;

@end

//RootViewController.m
- (UITableViewCell *) getCellContentView:(NSString *)cellIdentifier {

CGRect CellFrame = CGRectMake(0, 0, 300, 60);
CGRect Label1Frame = CGRectMake(10, 10, 290, 25);
CGRect Label2Frame = CGRectMake(10, 33, 290, 25);
UILabel *lblTemp;

UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:CellFrame reuseIdentifier:cellIdentifier] autorelease];

//Initialize Label with tag 1.
lblTemp = [[UILabel alloc] initWithFrame:Label1Frame];
lblTemp.tag = 1;
[cell.contentView addSubview:lblTemp];
[lblTemp release];

//Initialize Label with tag 2.
lblTemp = [[UILabel alloc] initWithFrame:Label2Frame];
lblTemp.tag = 2;
lblTemp.font = [UIFont boldSystemFontOfSize:12];
lblTemp.textColor = [UIColor lightGrayColor];
[cell.contentView addSubview:lblTemp];
[lblTemp release];

return cell;
}

The above method creates three rectangles: one for the cell and for the two labels in the cell. We create the cell using initWithFrame:reuseIdentifier, but this time we add two labels to the content view property. Initialize the first label with “Label1Frame” coordinates, which will make this label show up at the top of the cell. Set the tag of this label to 1, so we can find it in tableView:cellForRowAtIndexPath method. Initialize the second label using “Label2″Frame” coordinates, which will make this label to show up at the bottom. The tag of this label is set to 2. The labels are added to the content view using addSubView method. Finally the cell is returned to be reused.

Displaying data

Once we have the table view cell, we can get the underlying label by passing viewWithTag message to the cell. Here the data is set to the text property of the label and not the cell. This is how the code for tableView:cellForRowAtIndexPath changes

//RootViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if(cell == nil)
cell = [self getCellContentView:CellIdentifier];

UILabel *lblTemp1 = (UILabel *)[cell viewWithTag:1];
UILabel *lblTemp2 = (UILabel *)[cell viewWithTag:2];

if(searching) {

lblTemp1.text = [copyListOfItems objectAtIndex:indexPath.row];
lblTemp2.text = @"";
}
else {

//First get the dictionary object
NSDictionary *dictionary = [listOfItems objectAtIndex:indexPath.section];
NSArray *array = [dictionary objectForKey:@"Countries"];
NSString *cellValue = [array objectAtIndex:indexPath.row];

lblTemp1.text = cellValue;
lblTemp2.text = @"Sub Value";

[cellValue release];
}

return cell;
}

If you run this application now you will notice some obvious display issues, which we can resolve by specifying the height of the row. This is done in tableView:heightForRowAtIndexPath method. The code looks like this

//RootViewController.m
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {

return 60;
}

All the rows in the table view will have a height of 60. Run your app to see the changes.


Conclusion
In this tutorial we saw how easy it is to customize the table view cell. The same principle can be applied to customize the display by adding different controls to the content view. Instead of adding labels a progress bar can also be added. I hope this tutorial has helped you in some way.

Happy Programming,
iPhone SDK Articles


Attachments

Suggested Readings

SQLite Tutorial – Saving images in the database

There maybe times when you need to save more than just text in the database, like a file or an image. SQLite versions 3.0 and later allows you to store BLOB data in a column. Using the data type BLOB we can store large objects like an image or a file. Read on to learn more.

Introduction
Using BLOB data type we can store an image in the SQLite database. The data that actually gets stored in the database is the bytes that make up an image or a file. The is the sixth tutorial in SQLite tutorial series and borrows its source code from the previous one. We will change the “DetailView” by adding a new section which will be used to display or change the image. The image can only be changed when the view is in the edit mode. We will use a UIImagePickerController to display the photo album and to select an image.

Changing the database
Let’s start by adding a new column to our “Coffee” database. Name the column “CoffeeImage” and set its data type to BLOB. This is how the database schema looks like in SQLite manager


Changing the Coffee Class
Add a new property to the “Coffee” class which will hold the image from the database. This is how the header and the implementation files look like

//Coffee.h
//Complete code listing not shown
@interface Coffee : NSObject {

NSInteger coffeeID;
NSString *coffeeName;
NSDecimalNumber *price;
UIImage *coffeeImage;
//Complete Code listing not shown ...
}
@property (nonatomic, readonly) NSInteger coffeeID;
@property (nonatomic, copy) NSString *coffeeName;
@property (nonatomic, copy) NSDecimalNumber *price;
@property (nonatomic, retain) UIImage *coffeeImage;
//Complete Code listing not shown ...

The new property is synthesized and released in the dealloc method

//Coffee.m
//Complete code listing now shown
@synthesize coffeeID, coffeeName, price, isDirty, isDetailViewHydrated, coffeeImage;

- (void)setCoffeeImage:(UIImage *)theCoffeeImage {

self.isDirty = YES;
[coffeeImage release];
coffeeImage = [theCoffeeImage retain];
}

- (void) dealloc {

[coffeeImage release];
[price release];
[coffeeName release];
[super dealloc];
}

Changing the Detail View
Let’s add a new row to the detail view, which will be used to display the image or to change the image in edit mode. Since the style of the table view placed on the detail view is set to “Grouped”, we will return three sections in numberOfSectionsInTableView and this is how the source code looks like

//DetailViewController.h
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tblView {
return 3;
}

Let’s also display a title for the new section in tableView:titleForHeaderInSection. The code changes like this

//DetailViewController.m
- (NSString *)tableView:(UITableView *)tblView titleForHeaderInSection:(NSInteger)section {

NSString *sectionName = nil;

switch (section) {
case 0:
sectionName = [NSString stringWithFormat:@"Coffee Name"];
break;
case 1:
sectionName = [NSString stringWithFormat:@"Price"];
break;
case 2:
sectionName = [NSString stringWithFormat:@"Coffee Image"];
break;
}

return sectionName;
}

Displaying the photo album
Now we need to display a photo album when the section is selected in edit mode. From this album we can select a image to be saved in the database. Using a UIImagePickerController we can capture an image from the camera or the photo library on the device. UIImagePickerController class manages all the user interactions and all we have to do is display and dismiss it. Before we can display the view, we need to check if the interface is supported by calling isSourceTypeAvailable class method. The delegate of the UIImagePickerController should also confirm to UINavigationControllerDelegate and UIImagePickerControllerDelegate. In this case we will set the delegate of UIImagePickerController to “DetailViewController” and this is how the header and implementation file changes

//DetailViewController.h
@class Coffee, EditViewController;

@interface DetailViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate, UITableViewDataSource, UITableViewDelegate> {

IBOutlet UITableView *tableView;
Coffee *coffeeObj;
NSIndexPath *selectedIndexPath;
EditViewController *evController;

UIImagePickerController *imagePickerView;
}

@property (nonatomic, retain) Coffee *coffeeObj;

@end

The variable “imagePickerView” is released in the dealloc method and the code listing is now shown here. We will initialize “imagePickerView” in viewDidLoad method and this is how the code changes

//DetailViewController.m
- (void)viewDidLoad {
[super viewDidLoad];

self.navigationItem.rightBarButtonItem = self.editButtonItem;

//Configure the UIImagePickerController
imagePickerView = [[UIImagePickerController alloc] init];
imagePickerView.delegate = self;
}

From the above code, “imagePickerView” is initialized and its delegate is set to self. This way “DetailViewController” will be notified of all the user actions on the picker controller. First, we need to display the view and it is done in tableView:didSelectRowAtIndexPath and this is how the code looks like

//DetailViewController.m
- (void)tableView:(UITableView *)tblView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

//Keep track of the row selected.
selectedIndexPath = indexPath;

if(evController == nil)
evController = [[EditViewController alloc] initWithNibName:@"EditView" bundle:nil];

//Find out which field is being edited.
switch(indexPath.section)
{
case 0:
evController.keyOfTheFieldToEdit = @"coffeeName";
evController.editValue = coffeeObj.coffeeName;

//Object being edited.
evController.objectToEdit = coffeeObj;

//Push the edit view controller on top of the stack.
[self.navigationController pushViewController:evController animated:YES];
break;
case 1:
evController.keyOfTheFieldToEdit = @"price";
evController.editValue = [coffeeObj.price stringValue];

//Object being edited.
evController.objectToEdit = coffeeObj;

//Push the edit view controller on top of the stack.
[self.navigationController pushViewController:evController animated:YES];
break;
case 2:
if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary])
[self presentModalViewController:imagePickerView animated:YES];
break;
}
}

First confirm if the photo library is available on the device or not, if it is then present the view to the user. The method imagePickerController:didFinishPickingImage:editingInfo is called when an image is selected. This is how the code looks like

//DetailViewController.m
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)dictionary {

coffeeObj.coffeeImage = image;
[tableView reloadData];
[picker dismissModalViewControllerAnimated:YES];
}

We get the selected image and set the “coffeeImage” property of the selected coffee. The table view is then reloaded and the picker view is dismissed.

Display the coffee image
We will display the coffee image in tableView:cellForRowAtIndexPath and this is how the code looks like

//DetailViewController.m
- (UITableViewCell *)tableView:(UITableView *)tblView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}

switch(indexPath.section) {
case 0:
cell.text = coffeeObj.coffeeName;
break;
case 1:
cell.text = [NSString stringWithFormat:@"%@", coffeeObj.price];
break;
case 2:
cell.text = @"Change Image";
if(coffeeObj.coffeeImage != nil)
cell.image = coffeeObj.coffeeImage;
break;
}

return cell;
}

If the index of the section is 2 then we set the title of the cell to “Change Image” and the image to the coffee image.

Saving image in the SQLite database
Until now we haven’t saved the image in the database because we only save data when the application is being terminated or if a memory warning is received. The data is saved in “saveAllData” method as seen in the previous tutorial. Let’s change this method to save the image in the database which now has a new column called “CoffeeImage” to hold the image as bytes. This is how the code looks like

//Coffee.m
- (void) saveAllData {

if(isDirty) {

if(updateStmt == nil) {
const char *sql = "update Coffee Set CoffeeName = ?, Price = ?, CoffeeImage = ? Where CoffeeID = ?";
if(sqlite3_prepare_v2(database, sql, -1, &updateStmt, NULL) != SQLITE_OK)
NSAssert1(0, @"Error while creating update statement. '%s'", sqlite3_errmsg(database));
}

sqlite3_bind_text(updateStmt, 1, [coffeeName UTF8String], -1, SQLITE_TRANSIENT);
sqlite3_bind_double(updateStmt, 2, [price doubleValue]);

NSData *imgData = UIImagePNGRepresentation(self.coffeeImage);

int returnValue = -1;
if(self.coffeeImage != nil)
returnValue = sqlite3_bind_blob(updateStmt, 3, [imgData bytes], [imgData length], NULL);
else
returnValue = sqlite3_bind_blob(updateStmt, 3, nil, -1, NULL);

sqlite3_bind_int(updateStmt, 4, coffeeID);

if(returnValue != SQLITE_OK)
NSLog(@"Not OK!!!");

if(SQLITE_DONE != sqlite3_step(updateStmt))
NSAssert1(0, @"Error while updating. '%s'", sqlite3_errmsg(database));

sqlite3_reset(updateStmt);

isDirty = NO;
}

//Reclaim all memory here.
[coffeeName release];
coffeeName = nil;
[price release];
price = nil;

isDetailViewHydrated = NO;
}

We first rewrite the update query to include the CoffeeImage column. We also need a way to get the image data as bytes and this is where UIImagePNGRepresentation method helps us. We then bind the BLOB parameter using sqlite3_bind_blob method. The first parameter takes the update statement, the second parameter takes the index of the parameter value, the third one is the actual data itself, the fourth one is the length of the data which is being saved, and a pointer to a method which is responsible to clean up the data. If sqlite3_bind_blob method does not return SQLITE_OK then we display an error in NSLog. If there is no error while saving the data then we have successfully saved the image in the SQLite database on the iPhone/iPod.

Getting an image from the SQLite database
We now have to get the data from the database when the detail view is loaded. This is done in “hydrateDetailViewData” method and this is how the code changes

//DetailViewController.m
- (void) hydrateDetailViewData {

//If the detail view is hydrated then do not get it from the database.
if(isDetailViewHydrated) return;

if(detailStmt == nil) {
const char *sql = "Select price, CoffeeImage from Coffee Where CoffeeID = ?";
if(sqlite3_prepare_v2(database, sql, -1, &detailStmt, NULL) != SQLITE_OK)
NSAssert1(0, @"Error while creating detail view statement. '%s'", sqlite3_errmsg(database));
}

sqlite3_bind_int(detailStmt, 1, coffeeID);

if(SQLITE_DONE != sqlite3_step(detailStmt)) {

//Get the price in a temporary variable.
NSDecimalNumber *priceDN = [[NSDecimalNumber alloc] initWithDouble:sqlite3_column_double(detailStmt, 0)];

//Assign the price. The price value will be copied, since the property is declared with "copy" attribute.
self.price = priceDN;

NSData *data = [[NSData alloc] initWithBytes:sqlite3_column_blob(detailStmt, 1) length:sqlite3_column_bytes(detailStmt, 1)];

if(data == nil)
NSLog(@"No image found.");
else
self.coffeeImage = [UIImage imageWithData:data];

//Release the temporary variable. Since we created it using alloc, we have own it.
[priceDN release];
}
else
NSAssert1(0, @"Error while getting the price of coffee. '%s'", sqlite3_errmsg(database));

//Reset the detail statement.
sqlite3_reset(detailStmt);

//Set isDetailViewHydrated as YES, so we do not get it again from the database.
isDetailViewHydrated = YES;
}

The select query is changed to include CoffeeImage column and we use sqlite3_column_blob method to load the BLOB data into variable of type NSData. WE create an image of type NSData from imageWithData class method. The rest of the code works as described above.

This tutorial wasn’t designed to display imags anywhere and is a direct result of all the emails I got asking, how to save images in the SQLite database. So if you choose an image which does not fit in the section, it will take up all the space on the view.

Conclusion
This tutorial explains how to save and read images from a SQLite database. We can use the same functionality to save files in the database. I hope you had fun and learnt something new with this tutorial.

Happy Programming,
iPhone SDK Articles


Attachments

Suggested Readings

UITableView – Drill down table view tutorial

Until now we saw how to create a simple table but there are times when we need to show another table view when a row is selected. This kind of a flow is called “drill down” and in this tutorial we learn how to create a drill down table view. Click on “Read more…” to learn more

Introduction
Using a drill down table view we can display some primary level of items, where a selected row will result in displaying a new table view with data related to the item selected. The process is repeated until an item has no sub level categories and it is then we display a detail view which is not a UITableView. This is the seventh tutorial in the UITableView series and does NOT inherit its source code from the previous tutorial.

Creating the project
Create a new project in XCode by selecting “Navigation-Based Application”, I have named my project “DrillDownApp” which is used in this tutorial.

Using nothing but a single UITableView
Instead of creating 3 different UITableView’s to display three different levels, we will use the same table view and the same table view controller. This philosophy is borrowed from web programming, where we only have one page to display a list of products.

Designing the flow
Before we start writing any code, let’s decide the flow of the application with some specifics. Below is a table of how the data movies when we select an item.

First Level Second Level Third Level Fourth Level Fifth Level Sixth Level
Item 1 Screen A Detail View N/A N/A N/A
Item 2 Screen B Screen C Detail View N/A N/A
Item 3 Screen D Screen E Screen F Detail View N/A
Item 4 Screen G Screen H Screen I Screen J Detail View

When the application is launched we will see the following items; “Item 1”, “Item 2”, “Item 3”, and “Item 4”. If “Item 1” is selected then we see the table view display “Screen A” and upon selecting “Screen A” we will see the detail view load. The same goes for “Item 2”, which will load a table view with one value called “Screen B” -> “Screen C” -> Detail view and so on so forth for the remaining values in the first table view.

Creating the data source
The data source for this tutorial will come from an XML file or a plist file. Create a new file under the “Resource” folder by clicking on File -> New File -> (Under MAC OS X) select Other -> Property List and save this new file by giving it a name of “Data.plist”. Let’s start by adding values to this property list file.

Select the “Root” element and create a new item by clicking on the arrows at the right.


Name the item “Rows” and change its type to “Array”. This is the array which will contain all the primary level items with its children. The user should be able to select an item in a table view and load another table view with all items under the selected item in the first table view. For us to achieve this, our array should contain a dictionary with at least two types of fields; string and a array. The string type will hold the value of the cell and the array will hold the children rows, which again will contain a n number of dictionaries where n is the number of children rows an item will have.

Create a new item “Rows” and set its type to “Dictionary”, you do not have to change the key of the dictionary. Select the dictionary item and create a new item and set its data type to string and name the key to “Title”. The name of the key which should not be changed throughout the property list file, since this is the key which will contain the value of the cell which shows up in the UITableViewCell. Set its value to “Item 1” since this is the first item that shows up in the table view. From the above table we know that selecting “Item 1” should reload the table view to display “Screen A”. Create another field under “Item 1” and change its type to an Array and set its key to “Children”. Under the “Children” item create another item of type “Dictionary” which will only contain a string value called “Screen A” with its key set to “Title”. If “Screen A” is selected we load the detail view; so we do not have to add any children at this level. Add the remaining items from the table in the property list and when you are done the property list should look like this.

Populating the data source
Now that we have created the data source, the next step is to populate it which will be done in the application delegate. Since the root element of data.plist is of type “Dictionary”, we will create a variable of type NSDictionary to hold all the data from the property list file. The header file of the application delegate changes like this

//DrillDownAppDelegate.h
@interface DrillDownAppAppDelegate : NSObject <UIApplicationDelegate> {

UIWindow *window;
UINavigationController *navigationController;

NSDictionary *data;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) IBOutlet UINavigationController *navigationController;

@property (nonatomic, retain) NSDictionary *data;

@end

The variable “data” will hold our data source which is also synthesized in the implementation file and released in the dealloc method (code not shown here).

This is how the data is populated in applicationDidFinishLaunching method

//DrillDownAppDelegate.m
- (void)applicationDidFinishLaunching:(UIApplication *)application {

NSString *Path = [[NSBundle mainBundle] bundlePath];
NSString *DataPath = [Path stringByAppendingPathComponent:@"data.plist"];

NSDictionary *tempDict = [[NSDictionary alloc] initWithContentsOfFile:DataPath];
self.data = tempDict;
[tempDict release];

// Configure and show the window
[window addSubview:[navigationController view]];
[window makeKeyAndVisible];
}

We read the “data.plist” file and read all of its data in a dictionary object using initWithContentsOfFile message which is present in the NSDictionary class.

Display the data in the table view
Since we will be using the same table view to display data at level, we need; an array to hold the data for the current level, a string to hold the item selected in the parent level and an integer value to tell us if this is the first level or not. This is how the header file for RootViewController changes

//RootViewController.h
@interface RootViewController : UITableViewController {

NSArray *tableDataSource;
NSString *CurrentTitle;
NSInteger CurrentLevel;
}

@property (nonatomic, retain) NSArray *tableDataSource;
@property (nonatomic, retain) NSString *CurrentTitle;
@property (nonatomic, readwrite) NSInteger CurrentLevel;

@end

The property “tableDataSource” contains the present level data source and mimicks the property list file. The array will contain n number of dictionary objects, where n is the number of rows at any given level. A dictionary will contain a title and an array containing the children of the present item. All the properties have been synthesized and the “tableDataSource” and “CurrentTitle” have been released in the dealloc method.

The method viewDidLoad changes like this

//RootViewController.m
- (void)viewDidLoad {
[super viewDidLoad];

if(CurrentLevel == 0) {

//Initialize our table data source
NSArray *tempArray = [[NSArray alloc] init];
self.tableDataSource = tempArray;
[tempArray release];

DrillDownAppAppDelegate *AppDelegate = (DrillDownAppAppDelegate *)[[UIApplication sharedApplication] delegate];
self.tableDataSource = [AppDelegate.data objectForKey:@"Rows"];

self.navigationItem.title = @"Root";
}
else
self.navigationItem.title = CurrentTitle;
}

If the value of the “CurrentLevel” is zero (which it will be when the application is launched) then the array is initialized and populated from the application delegate. Here we get the array whose key is set to “Rows” which will contain the primary level data and the children of every item. The title of the navigation item is set to “Root” in this case, if not the title is set to the “CurrentTitle” variable and the “tableDataSource” is left untouched. These two properties are set in tableView:didSelectRowAtIndexPath method which will look at it later.

Displaying data
In tableView:numberOfRowsInSection return the count of the array like this

//RootViewController.m
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.tableDataSource count];
}

The method tableView:cellForRowAtIndexPath changes like this

//RootViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *CellIdentifier = @"Cell";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}

// Set up the cell...
NSDictionary *dictionary = [self.tableDataSource objectAtIndex:indexPath.row];
cell.text = [dictionary objectForKey:@"Title"];

return cell;
}

From the data source that we created, we know that every array has at least one element of type “Dictionary” and that has at least one element with the key “Title” which is the string that gets displayed in the table view cell. After the UITableView cell is created, we get the dictionary object from the array and the value for the key “Title”, which is set to the text property of the cell.

Select a row
When a row is selected tableView:didSelectRowAtIndexPath method is called and this is how it looks like

//RootViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

//Get the dictionary of the selected data source.
NSDictionary *dictionary = [self.tableDataSource objectAtIndex:indexPath.row];

//Get the children of the present item.
NSArray *Children = [dictionary objectForKey:@"Children"];

if([Children count] == 0) {

}
else {

//Prepare to tableview.
RootViewController *rvController = [[RootViewController alloc] initWithNibName:@"RootViewController" bundle:[NSBundle mainBundle]];

//Increment the Current View
rvController.CurrentLevel += 1;

//Set the title;
rvController.CurrentTitle = [dictionary objectForKey:@"Title"];

//Push the new table view on the stack
[self.navigationController pushViewController:rvController animated:YES];

rvController.tableDataSource = Children;

[rvController release];
}
}

We first get dictionary object of the selected row and then get its children into an array. If the present level does not have any children we do not do anything for now, but if it does; we load the same table view with different set of data. We initialized the same “RootViewcontroller”, increment the “CurrentLevel” value so it is not zero, set the selected cell’s text as the “CurrentTitle”, push the view controller on the top of the stack and set the new data source to the “tableDataSource” property.

Try it out and you will be able to drill down by selecting a row.

Loading the detail view
Create a new view (using IB) and name it “DetailView”, remember the views get stored under the “Resources” folder. Create a new UIViewController in Xcode under the “Classes” folder and name it DetailViewController. Set the class of File’s owner of the “DetailView” to “DetailViewController” and connect the view outlet to the object in the view in the nib file. Please refer to this tutorial on how to load a detail view.

In viewDidLoad method of the “DetailViewController” set the title of the view to “Detail View”. This is how the code looks like

//DetailViewController.m
- (void)viewDidLoad {
[super viewDidLoad];

self.navigationItem.title = @"Detail View";
}

We will load the detail view when an item does not have any children. The tableView:didSelectRowAtIndexPath method changes like this

//RootViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

//Get the dictionary of the selected data source.
NSDictionary *dictionary = [self.tableDataSource objectAtIndex:indexPath.row];

//Get the children of the present item.
NSArray *Children = [dictionary objectForKey:@"Children"];

if([Children count] == 0) {

DetailViewController *dvController = [[DetailViewController alloc] initWithNibName:@"DetailView" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:dvController animated:YES];
[dvController release];
}
else {

//Prepare to tableview.
RootViewController *rvController = [[RootViewController alloc] initWithNibName:@"RootViewController" bundle:[NSBundle mainBundle]];

//Increment the Current View
rvController.CurrentLevel += 1;

//Set the title;
rvController.CurrentTitle = [dictionary objectForKey:@"Title"];

//Push the new table view on the stack
[self.navigationController pushViewController:rvController animated:YES];

rvController.tableDataSource = Children;

[rvController release];
}
}


Conclusion

Using this design we can add any number of children to an item without changing the code. If we have to add data to the sixth level we need not create another table view, all we have to do is add another item in our data source and the code will handle the change. This design does use n number of table view objects like the example provided by Apple.

I hope you had fun reading this article as much as I had writing it. Please let me know what you think.

Happy Programming,
iPhone SDK Articles


Attachments

Suggested Readings

SQLite and CoreData

Yes, Apple has finally ported Core Data to the iPhone which means using SQLite in an application is not the best approach. Once the OS 3.0 is officially released, I will be posting more tutorials on using Core Data. Thank you for all your continued support.

Yes, Apple has finally ported Core Data to the iPhone which means using SQLite in an application is not the best approach. Once the OS 3.0 is officially released, I will be posting more tutorials on using Core Data. Thank you for all your continued support. continued support.

Happy Programming,
iPhone SDK Articles

Drill down table view with a detail view

Since I published my drill down table view tutorial, I got a lot of positive feedback but with one common request. How do I load a different detail view for an item or how do I load a UITabBarController in the detail view. In this tutorial I will show how to load a different detail view.

Introduction
Using a drill down table view we can display hierarchical data where the last view is responsible of displaying some detail information. This view in which the detail information is displayed can be the same for all the data in the table view (like my last tutorial) or it can be different based on the path that the user took. In this tutorial I will show you how to add a different detail view based on the path the user took to get to the last item. This tutorial is based on the UITableView – Drill down table view tutorial and borrows its source code.

This is how one of the detail view looks like
Loading a detail view
Let’s take a step back and think about the browser and the HTML it renders. The browser doesn’t know what it is going to display and it does not even know the style it should set for the HTML. All that information is present in the HTML and the browser simply renders everything on the screen.

Does this mean that to display different data, I need different views even though the view looks the same? No, if the view looks the same you can reuse the same view again to display different data.

Let us think about the navigation controller as the browser and the data in the plist file as the HTML. If we want to tell the browser to style certain elements of the page, we would put that information in the HTML. In a similar fashion, we can put the detail view information in the plist file which the navigation controller will display. You only need to set this information in the last node of the plist file. When it is time to display the detail view, the code can look at this field and show the right detail view to the user.

Let us change the plist file from the last tutorial to include some information about the detail view.

Changing the plist file
Here we will add a new item to the last child; the title that gets displayed before the detail view is shown. The item will have a title of “View”, type will be number and the value will be 1, 2, or 3. Where 1 = display a UITabBarController in the detail view, 2 = display a detail view with an image in it, and 3 = display a simple detail view.

Your plist file should look like this for the four items

Recap
We have added an extra item at the end of called “View” whose type is integer and value is either 1, 2, or 3. Where 1 = load a detail view UITabBarController, 2 = load a detail view with an image and 3 = load a simple detail view. Let’s see how to add a detail view with a UITabBarController

Adding a UITabBarController to the detail view
Let’s add a UITabBarController to the detail view. We will need an outlet of type UITabBarController, so lets create that in the header file of RootViewController. The code changes like this

//RootViewController.h
@interface RootViewController : UITableViewController {

NSArray *tableDataSource;
NSString *CurrentTitle;
NSInteger CurrentLevel;
IBOutlet UITabBarController *tbController;
}

@property (nonatomic, retain) NSArray *tableDataSource;
@property (nonatomic, retain) NSString *CurrentTitle;
@property (nonatomic, readwrite) NSInteger CurrentLevel;

@end

The dealloc method changes like this

//RootViewController.m
- (void)dealloc {
[tbController release];
[CurrentTitle release];
[tableDataSource release];
[super dealloc];
}

Launch Interface Builder by double clicking “RootViewController.xib” file and drag and drop a UITabBarController in the nib file. Select File’s Owner and select the outlet “tbController” and drag it over to the UITabBarController and release. Now we have connected the outlet tbController to the UITabBarController. Add two views in the UITabBarController and change the title of the UITabBarItems to say “View One” and “View Two”. Also add a label to both the views which would say “View One” for the first and “View Two” for the second. This is all you have to do in the Interface Builder.

Display the detail view
We now have to display the UITabBarController as a detail view when an item is selected in the UITableView. Before we do that let’s stop for a while and see what we did. We added a UITabBarController in the same nib file as the RootViewController instead of creating a different nib file. When displaying a detail view, normally we would initialize the view controller and push it on top of the navigation controller. If we try to do the same here we would see the same table view again, which we do not want. The trick here is to switch the view of the RootViewController from the table view to the view of the tbController. Let’s see how to do this; the code for tableView:didSelectRowAtIndexPath changes like this

//RootViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

//Get the dictionary of the selected data source.
NSDictionary *dictionary = [self.tableDataSource objectAtIndex:indexPath.row];

//Get the children of the present item.
NSArray *Children = [dictionary objectForKey:@"Children"];

if([Children count] == 0) {
NSInteger ViewNumber = [[dictionary objectForKey:@"View"] integerValue];
switch (ViewNumber) {
case 1: {
RootViewController *rvc = [[RootViewController alloc] initWithNibName:@"RootViewController" bundle:[NSBundle mainBundle]];
//Switch the view here
rvc.view = tbController.view;
[self.navigationController pushViewController:rvc animated:YES];
[rvc release];
}
break;
case 2:
break;
case 3: {
DetailViewController *dvController = [[DetailViewController alloc] initWithNibName:@"DetailView" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:dvController animated:YES];
[dvController release];
}
break;
default: {
DetailViewController *dvController = [[DetailViewController alloc] initWithNibName:@"DetailView" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:dvController animated:YES];
[dvController release];
}
break;
}
}
else {

//Prepare to tableview.
RootViewController *rvController = [[RootViewController alloc] initWithNibName:@"RootViewController" bundle:[NSBundle mainBundle]];

//Increment the Current View
rvController.CurrentLevel += 1;

//Set the title;
rvController.CurrentTitle = [dictionary objectForKey:@"Title"];

//Push the new table view on the stack
[self.navigationController pushViewController:rvController animated:YES];

rvController.tableDataSource = Children;

[rvController release];
}
}

That is the complete code listing for tableView:didSelectRowAtIndexPath method but it does a lot of things and we only need to concentrate on some of the code, so teh condensed version is listed below

//RootViewController.m - tableView:didSelectRowAtIndexPath
if([Children count] == 0) {
NSInteger ViewNumber = [[dictionary objectForKey:@"View"] integerValue];
switch (ViewNumber) {
case 1: {
RootViewController *rvc = [[RootViewController alloc] initWithNibName:@"RootViewController" bundle:[NSBundle mainBundle]];
//Switch the view here
rvc.view = tbController.view;
[self.navigationController pushViewController:rvc animated:YES];
[rvc release];
}
break;
case 2:
break;
case 3: {
DetailViewController *dvController = [[DetailViewController alloc] initWithNibName:@"DetailView" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:dvController animated:YES];
[dvController release];
}
break;
default: {
DetailViewController *dvController = [[DetailViewController alloc] initWithNibName:@"DetailView" bundle:[NSBundle mainBundle]];
[self.navigationController pushViewController:dvController animated:YES];
[dvController release];
}
break;
}
}

The above code first checks if the present item has any children or not, if it doesn’t then we look for the value of the item “View”. If it is 1, we know that we have to display a detail view with a UITabBarController. As mentioned before we need to switch the view property of the controller to the view of the tab bar controller, which we do after initializing the controller. At last we ask the navigation controller to display our view which is connected to the controller.

Test it out to see how it works.

We also mentioned that if the “View” has a value of 3 then we would display the simple detail view. So drill down Item 3/4 and see the result. Don’t do that with Item 2 yet because it will not work.

Adding a detail view which will display an image.

Hopefully by now you have a clear idea of how to display different detail views when working with a drill down app.

To display a view with an image create a new view and a new view controller and name the view “ImageView” and the view controller “ImageViewController”. Open the ImageView in IB and set the class to be “ImageViewController” and connect the view outlet from the File’s Owner to the view. Drag and drop a UIImageView from the library on the view. We will set the image of this UIImageView when the view loads and the name of the image will be passed from the root view controller. Since we need to display an image in the image view from Xcode we need an outlet of type UIImageView in the Interface Builder. The header file of “ImageViewController” changes like this

//ImageViewController.m
@interface ImageViewController : UIViewController {

IBOutlet UIImageView *imgView;
NSString *ImageName;
}

@property (nonatomic, retain) NSString *ImageName;

@end

We have also declared a property called “ImageName” which the root view controller will use to pass the name of the image to the image view controller and the image view controller will display the image in the image view.

This is how the viewDidLoad method changes

//ImageViewController.m
- (void)viewDidLoad {
[super viewDidLoad];

NSString *Path = [[NSBundle mainBundle] bundlePath];
NSString *ImagePath = [Path stringByAppendingPathComponent:ImageName];
UIImage *tempImg = [[UIImage alloc] initWithContentsOfFile:ImagePath];
[imgView setImage:tempImg];
[tempImg release];
}

The dealloc method looks like this

//ImageViewController.m
- (void)dealloc {
[ImageName release];
[imgView release];
[super dealloc];
}

Displaying the detail view
Let’s go back to the RootViewController.m file and import “ImageViewController.h” file at the top and it should look like this

//RootViewController.m
#import "RootViewController.h"
#import "DrillDownAppAppDelegate.h"
#import "DetailViewController.h"
#import "ImageViewController.h"

@implementation RootViewController
...

In tableView:didSelectRowAtIndexPath we will now display the image detail view when the “ViewNumber” is set to 2. This is how the code looks like

//RootViewController.m - tableView:didSelectRowAtIndexPath method
case 2: {
ImageViewController *ivc = [[ImageViewController alloc] initWithNibName:@"ImageView" bundle:[NSBundle mainBundle]];
ivc.ImageName = @"Image.jpg";
[self.navigationController pushViewController:ivc animated:YES];
[ivc release];
}
break;

In the above code we initialize the “ImageViewController” and set the name of the image that it should display and ask the navigation controller to display it. Do not forget to place an image in the “Resources” folder.

Build and Run to see how it works.

Conclusion
I hope this helps you in some way on how to load a detail view with a UITabBarController. If you have any questions on loading a different detail view please take a look at this tutorial. Comments are most welcome and if you have any questions please send me an email at iphonearticles[@]gmail[dot]com.

Happy Programming,
iPhone SDK Articles


Attachments

Suggested Readings

Localizing iPhone Apps – Internationalization

By localizing an iPhone app, we display cultural information in the user’s specified locale, but what about the text, like the application name and displaying visible information in user’s preferred language. In this tutorial, I will show you how we can use resource files to display visible text in user’s language.

Introduction
Localization and Internationalization are complimentary activities, with one we can display cultural information like units, dates in the user’s preferred locale and the other, let’s us display text in the user’s preferred language.

The first step is to find out what languages your application is going to support and gather all the text (in different languages), images, videos, sounds and put them in a resource file or in a language directory. To translate text into different languages you can a tool that Google provides here. Even if you do not plan to support different languages in your app, it is a good idea to get it set up front.

The user can change the language by going to Settings -> General -> International -> Language. When the language is changed, the iPhone also changes the name of the application, only if the application supports it.

Preparing for Internationalization
In our sample app, we are going to support two languages English and Italian. Create a new project in Xcode, does not matter what template you select. After you have created your project, open the project location in Finder and create two directories called en.lproj and it.lproj. These two directories become the language project for your application. All the English language resources will stored in the folder en.lproj and the Italian language resources will be stored in it.lproj folder. The resource files that contain the localizable string are called “strings” file and their default name is “Localizable.strings”. So, we will create two new strings file in Xcode, select Resources and click on File -> New File -> Other (under Mac OS X) -> Strings file and click on Next, name your file “Localizable.strings” and save it in en.lproj directory. Repeat the same process by saving it in it.lproj directory. This is how the files look like in Xcode, since Xcode is smart enough to figure out that the file is localized for two different languages.

Localizing strings file
Strings are localized in the format “key” = “value”;. The key and the value is in double quotes followed by a semi-colon. You can also add comments above every key-value pair, so the resource file is properly documented. Let’s add a key-value resource in English and Italian as follows in its own Localizable.strings file

//Localizable.String file for the English version.
"WelcomeKey" = "Welcome!!!";
//Localizable.strings file for the Italian version.
"WelcomeKey" = "Benvenuto!!!";

Let’s look at some code on how to get these values from the resource file and display it.

- (void)applicationDidFinishLaunching:(UIApplication *)application {

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSArray *languages = [defaults objectForKey:@"AppleLanguages"];
NSString *currentLanguage = [languages objectAtIndex:0];

NSLog(@"Current Locale: %@", [[NSLocale currentLocale] localeIdentifier]);
NSLog(@"Current language: %@", currentLanguage);
NSLog(@"Welcome Text: %@", NSLocalizedString(@"WelcomeKey", @""));

// Override point for customization after application launch
[window makeKeyAndVisible];
}

We get the list of languages by using the key AppleLanguages and then get the first language which is the user’s preferred language. We then display some basic information like the locale, language and then display the localized string using NSLocalizedString function, which takes two parameters a key and comment which you can leave it blank. The comment parameter does serve a purpose when we want to generate the strings file automatically. Nowhere in the function we specify, from which language directory we want to display the localized string, it is what we can call language sensitive. If it cannot find the preferred language directory then it picks up second language from the list and so on.

Creating the resource file using genstrings
Using the genstrings tool, we can create the strings file automatically. Be sure to use NSLocalizedString function every where in your code to display the localized text for a given key. When the tool is used on the source files, it will extract all the keys, comments and create a strings file for you, where the comment is set to be the value for the key. All you then have to do is change the value for the keys according to the the language. You can run the command line tool like this to generate “Localizable.strings” file under en.lproj directory.

genstrings -o en.lproj *.m

Before you run the command, make sure the target directory exists.

Localizing iPhone display name
When the user changes its preferred language, the iPhone will also change the display name for all the apps, if it supports that language. Let’s see how we can display the app name in Italian, in English our sample app is called “StringsFile”. Create new strings file and save it in it.lproj directory, with the name “InfoPlist.strings”. Add a new entry with the key “CFBundleDisplayName” without quotes and set the value to “Stringhe di file” (I know not a proper translation, but you get the idea). This is how the file should look like

CFBundleDisplayName = "Stringhe di file";

Now change your preferred language to Italian and the name of your application will be changed. Once you change the language back to English it will say “StringsFile”, we do not need to create a new InfoPlist.strings file for English.

Conclusion
Since your iPhone app can be downloaded by anyone in any Country, it becomes extremely important that the app looks and behaves according to the user’s locale and language. I hope this tutorial has helped you in localizing your apps.

Happy Programming,
iPhone SDK Articles


Attachments

Suggested Readings