Adding a UIImage above a TTLauncherView

I had a project recently where there needed to be an image at the top of the view with a TTLauncherView underneath it.

I struggled with this at first as I could see no mention of doing this in the Three20 documentation. In the end I had to actually create the UIImageView and add it manually to the view.

The first thing you have to do is declare a UIImageView in the header file.

@property(nonatomic, retain) UIImageView *logoImageView;

Then in the implementation file, synthesize the property.

@synthesize logoImageView;

Now we modify the loadView method to add the code needed to create and insert the logoImageView.

Allocate and initialise the logoImageView.

self.logoImageView = [[UIImageView alloc] initWithFrame:CGRectMake(65, 0, 190, 108)];

I’ve used hard coded values to define the location and size of the UIImageView. I got these values by just drawing up a simple diagram and working it out based on the iPhones screen size.

Diagram of where UIImageView will be placed<br />

It would be much better to derive these values based on the views bounds.

Once the size / position of the UIImageView is done we go ahead and set the actual image file that we want displayed in the logoImageView

self.logoImageView.image = [UIImage imageNamed:@"logo_small.png"];

Now we need to modify the size of the Three20 launcherView so that it fits on the view properly now that we have a UIImageView being placed above it. We do this by modifying the line of code that creates the TTLauncherView.

launcherView = [[TTLauncherView alloc] initWithFrame:CGRectMake(0, 120, 320, 352)];

Previously we were setting the frame for the TTLauncherView to be the bounds of the view. Now we have to actually specify a manual frame size and position. I worked out these values from the diagram as well. Again, it would be better to derive these values based on the views bounds.

Once that is done, we just have to add the UIImageView to the view. Near the bottom of the loadView method is the code for adding the launcherView to the view

[self.view addSubview:launcherView];
[launcherView release];

Right before these lines, add some code to insert the UIImageView onto the view.

[self.view addSubview:logoImageView];
[logoImageView release];

Now you should be able to run your app and see the UIImageView above the TTLauncherView. I found on my first try that the TTLauncherItem buttons were being cut off because my logo was too big. I simply made the image smaller and recalculated the size / location to place the views.

Three20 has methods to modify the size of the TTLauncherItems, but I didn’t want to complicate things.

There’s a bunch of ways to make this better, but it works.

Using TTLauncherView

After my little rant about Three20 in a previous post I figured I should at least try to make things better for other people.

One of the first things I struggled with was getting the TTLauncherView working. I had followed the steps on the Three20 website to add the necessary files to the project but I wasn’t sure how I then got the TTLauncherView displaying on the screen.

So, to hopefully help someone else… here goes.

Start off in your AppDelegate.m file. It should have been created automatically for you when you created your project in XCode. First thing you need to do is actually include the Three20 header file.

#import "Three20/Three20.h"

I’m sure you could probably just import the TTLauncherView header file but I figured I’d just make it easier on myself and include the main Three20.h file.

After that, you need to setup the TTNavigator and the relevant view controller mappings. All this stuff needs to be done in the

-(void)applicationDidFinishLaunching:(UIApplication *)application

method.

Create the TTNavigator instance

TTNavigator *navigator = [TTNavigator navigator];

Set the navigator persistence mode

navigator.persistenceMode = TTNavigatorPersistenceModeNone;

My understanding of this is that Three20 actually keeps a record of where you are in the navigation while the app is running. This means when you close the app and reopen it, Three20 can start off from where you left it. In this case I am telling Three20 to not do that and instead the app will just start up as normal.

The next step is to setup the TTURLMap. Three20 has this neat feature where you can refer to view controllers using a URL. This actually makes working with view controllers a lot simpler, at least once you get the hang of it. NOTE: This also means you are pretty much locked into using Three20 once you start using TTURLs. If you get part way and decide you no longer want to use Three20 you are going to have to re-write everything.

Get a pointer to the navigators URL map.

TTURLMap *map = navigator.URLMap;

Use that pointer to setup all your URL mappings

[map from:@"*" toViewController:[TTWebController class]];
[map from:@"tt://launcher" toSharedViewController:[LauncherViewController class]];
[map from:@"tt://first" toViewController:[FirstViewController class]];
[map from:@"tt://second" toViewController:[SecondViewController class]];
[map from:@"tt://third" toViewController:[ThirdViewController class]];
etc
etc

After doing this you can then use the URL tt://launcher in your code to refer to your LauncherViewController (or whatever you have called it). I’ll give an example of this in a second.

Each of the ViewControllers referenced in the mapping code are standard UIViewController classes. You can actually map the URLs to any class.

After that you can actually get the launcher view controller to displaying using

[navigator openURLAction:[TTURLAction actionWithURLPath:@"tt://launcher"]];

One line of code instead of the usual 3 or 4 thats regularly needed to push a view controller onto the navigation stack. You can see there how using the URL makes things a lot easier. It took me a while to get my head around this.

So when my app starts, it will run the applicationDidFinishLaunching method and setup the mappings and then get Three20 to launch the tt://launcher URL, which is mapped to the LauncherViewController class.

Now, on to the LauncherViewController file.

My LauncherViewController is a subclass of TTViewController. I believe it has to be in order to crate the launcher view.

In the LauncherViewController.m file we setup the Launcher View in the loadView method

First we allocate and initialise the launcherView using the views bounds. This means the launcher view will take up the whole screen.

launcherView = [[TTLauncherView alloc] initWithFrame:self.view.bounds];

Then we set the background color of the launcher view.

launcherView.backgroundColor = [UIColor whiteColor];

Then we set the number of columns we want

launcherView.columnCount = 1;

In this example I’m only having one column with 3 items. You can adjust this value at anytime and Three20 will automatically reposition the icons in your launcherView to fit.

The next part involves setting up the actual items that will be displayed in the launcherView. You do this by setting the pages property of the launcherView instance.

launcherView.pages = [NSArray arrayWithObjects:
                        [NSArray arrayWithObjects:
                      [self launcherItemWithTitle:@"First" image:@"bundle://firsticon.png" URL:@"tt://first"],
                      [self launcherItemWithTitle:@"Second" image:@"bundle://secondicon.png" URL:@"tt://second"],
                       [self launcherItemWithTitle:@"Third" image:@"bundle://thirdicon.png" URL:@"tt://third"],
                       nil], nil];

This creates our pages structure. It’s an array of arrays where the inner array represents the icons on the page and the outer array represents the page. So you could define a structure that has any number of pages and on each page you have a number of icons.

Next thing we have to do is set the launcherViews delegate. In this case I’m setting the LauncherViewController as the delegate.

launcherView.delegate = self;

Once that’s done, we can add the launcherView to the LauncherViewControllers view.

[self.view addSubview:launcherView];

Then release the launcherView instance

[launcherView release];

When creating the pages above, I used a separate method to make things less cluttered. The launcherItemWithTitle method looks as follows:

-(TTLauncherItem *)launcherItemWithTitle:(NSString *)pTitle image:(NSString *)image URL:(NSString *)url
{
    TTLauncherItem *launcherItem = [[TTLauncherItem alloc] initWithTitle:pTitle image:image URL:url canDelete:YES];
    return [launcherItem autorelease];
}

All this is doing is taking the items passed in an creating a TTLauncherItem from them.

With this done, all thats left to do is to implement the necessary delegate methods.

- (void)launcherView:(TTLauncherView*)launcher didSelectItem:(TTLauncherItem*)item
{
    [[TTNavigator navigator] openURLAction:[TTURLAction actionWithURLPath:item.URL]];
}

This method is called when an item is selected from the launcherView. It’s quite simple really. Using the mappings we created earlier we simply tell the TTNavigator instance to open the URL. We create the URLAction required using the URL we set when creating each TTLauncherItem.

What this means is that when an item is tapped in the launcherView we want it to load the relevant View Controller. This will push the relevant view controller onto the navigation stack and it will be displayed.

I think that pretty much covers a basic LauncherView implementation. It’s not really that hard once you understand the way Three20 handles navigation.

It’s a lot to get your head around especially if you are starting out in iOS development. Stick with it though.

My thoughts after using Three20 for an iOS project

I’ve recently completed an iOS project where I decided to use Three20 to implement a launcher style view.
I’d read about a lot of apps that use Three20 and thought it would be a good framework to get the hang of. Little did I know the struggle that I would have.

The first thing I had trouble with was finding good documentation. There seems to be a lot of example around the web on all sorts of Three20 stuff but none of it is really catered for beginners. I struggle for a long time just trying to get it installed and working properly. I could’t even find a simple up and running tutorial.

The second thing I had trouble with was getting community help. I subscribed to the Three20 mailing list and also joined the irc channel. Numerous times I posted asking for help on the mailing list only to have it go unanswered. My attempts to start conversations and ask for help on IRC also failed. I even sat in the channel for a number of days, every so often saying hi or hello, but no-one replied.

In the end I gave up on the mailing list and also IRC.

I stumbled my way through the app development process all the while feeling like I was being held back by Three20 rather than being empowered. It got to the stage where I felt locked into using Three20 due to my use of it URL navigation scheme. I did try to find an easy way to remove Three20 from my app but that also was a struggle that I gave up on and just battled on.

In a way it did make me learn it better, but overall I’ve hated the experience and its actually turned me off ever using another complex framework again.

In the future I’ll be sticking with simple frameworks that do a very specific thing. For everything else I’ll use the native iOS API’s and manually build what I need. At least the I know I’ll have more luck with finding help if I need it.

Perhaps once my skills get better, using Three20 (or Nimbus) will be a easier. I highly recommend beginners steer clear of it though.

Custom NSTableViewCell labels not appearing

I’m working on an iPhone app at the moment that is being upgraded from a previous version. I decided early on to just build on the existing code rather than trying to reinvent the wheel.

I’ve spent a bit of time fixing warnings that have cropped up from changes in the iOS SDK (the original app was developed for iOS 2) but one problem in particular had me stumped for a while.

When subclassing NSTableViewCell, the old init method was:

- (id)initWithFrame:(CGRect)frame reuseIdentifier:(NSString *)reuseIdentifier

but somewhere along the line the init method definition changed to:

-(id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier

I’m yet to find out when this changed occurred, but it left me with the problem of no longer having access to the “frame” parameter. This meant the code in the init method that used to size the views of the custom cell no longer had a point of reference.

It took me a while to figure out that due to this change there was also another new method introduced that now had to be overridden to size the custom cell views:

-(void)layoutSubviews

In this method is where we are now meant to set the size of the custom cell views.

So I commented out the sizing code that used to be in the init method and moved it to the new layoutSubviews method as follows:

-(void)layoutSubviews
{
    [super layoutSubviews];
    CGRect label, steps;
    CGRect bounds = [[self contentView] bounds];
    label.origin.x = 20.0;
    label.origin.y = 3.0;
    label.size.height = bounds.size.height - (label.origin.y*2);
    label.size.width = 160.0;
    [dateLabel setFrame:label];

    steps.origin.x = 170.0;
    steps.origin.y = 5.0;
    steps.size.height = bounds.size.height - (steps.origin.y*2);
    steps.size.width = 120.0;
    [stepsLabel setFrame:steps];
}

This fixed my issue. Running the app now showed the custom cell labels as expected.