Fancy UILabels

People have asked which part of our Twitterrific application for the iPhone was hardest to develop. There were many challenges, but the one I found most onerous was scrolling in the UITableView.

The code we shipped in 1.0 was obviously flawed. Scrolling was jerky. We weren’t happy with it and neither were users.

There was no shortage of “expert” opinions on what was causing it. Some thought it was the loading of the avatar images, others thought it was the fancy backgrounds. One reviewer on iTunes even suggested that I could spend a couple of hours reading documentation on Apple’s site and fix it up.

Yeah, right.

The root of the problem was having links inline with the text: there are currently no attributed strings in the iPhone SDK. Before displaying a table row, I had to scan the text of the tweet for URLs and screen names, measure the length of text, and then create a bunch of views that approximated what people were used to seeing as HTML. This overwhelmed the CPU on the device: the GPU was having no problem compositing the layers. I had heeded the advice of experts and was careful to flatten the hierarchy as much as possible and to use opacity sparingly: only NSString’s sizeWithFont: was killing me.

So what changed in the 1.1 release? A slight interaction change and a new approach to rendering the UIButtons used for the links in the text.

The interaction change was that no text was styled when a table row is unselected. This allowed us to use a plain UILabel for the text of the tweet. Since only one tweet is selected at any time, this simplified the text layout requirements when reusing a table row.

Since UILabel was being used for rendering the text, our approach for measuring text changed as well. Instead of controlling all the text layout, I had to adapt my code to use metrics and line breaking that matched those used in UILabel’s implementation. They call it reverse engineering, kids.

This approach is far from perfect. There are cases where the buttons are placed incorrectly: I suspect that the cause of these problems is how round-off error is being handled in the method used by UILabel’s underlying WebView and the method used by sizeWithFont:. A half-pixel being rounded up versus down can cause a line break or the button text to not align with the underlying label. These errors seem to occur more frequently with Asian fonts.

There is also an issue with a URL that wraps a line: the button is only created for the first part of the URL (leading some users to think that the button won’t work—it will.)

In essence, it’s a hack. And useful one until Apple deals with Radar ID# 5834539 (titled “There is no way to style a run of text in a UILabel or UITextView”.) It wouldn’t hurt to dupe it if you’d like to see this aspect of the iPhone SDK improve.

Now that the FABULOUS NDA is a thing of the past, there’s no reason why you should have to figure this stuff out like I did. Here’s my implementation. Knock yourselves out customizing this code for your own needs. All I ask is that if you make any improvements in createButtons:, please let me know (my contact info is on my résumé page.)

But wait, there’s more!

And an extra added bonus, this project also shows you how to use regular expressions using RegexKitLite and libicucore. Extra pattern matching goodness, for all your iPhone needs.

Damn it feels good to leave out that NDA disclaimer at the bottom of this essay.

Updated December 4th, 2008: In the original essay, I forgot to link to the RegexKitLite page at SourceForge. This code by John Engelhart is so damn useful, I tend to think about it as a standard part of OS X. Take a moment and check out this project’s exemplary documentation.

[REDACTED]

Thank God—that’s the last time I’m going to type that word for awhile. The meme is dead, long live the SDK.

As a way to celebrate the lifting of the NDA, we bring you some very special source code. To wile away the time between our product submission and the launch of the App Store, my buddy Anthony Piraino and I worked on this very special treat. Something that will be familiar to all developers who have had to keep their mouths shut since March 6th, 2008. Just compile the source code and install it on your device. Typing pleasures await.

(You’ll need to install Twitterrific from the App Store to get the full user experience. But you’ve done that already, right?)

Besides being a fun inside joke, this very special application also shows an important aspect of iPhone development: URL schemes.

As we’ve seen many other times, the needs of a mobile user are very different than those of a desktop user. On the desktop, tight integration of several application domains makes applications like Coda a joy to use.

On the phone it’s better to focus on one task. From what I’ve seen, the best iPhone applications do one thing and do it well. Supporting URL schemes in your application makes that single task more attractive to other developers and users. It leads to what my friend Daniel Jalkut has aptly called the “Un-Coda-fication” of iPhone apps.

The benefit for a developer is obvious: it minimizes the scope of an application and the attendant memory footprint. You could write your own Twitter update code using a NSURLConnection, or you could use one line of code like this:

[[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"twitterrific:///post?message=EASY"]];

There is a less obvious, but equally important benefit when you look at URL schemes from a user’s point-of-view. Since your application’s scope is limited to one task, users will depend on it when they want to perform that task. Even if that task is in the context of another application.

An example of this is sharing photos. Users know that the Camera application takes pictures and that the Mail application sends messages. You don’t see a camera button in Mail; you see an “Email Photo” button in your Camera Roll. The user’s first task it to take a picture and the next task is to mail it.

Since we’re not Apple, we can’t achieve the high level of integration between the Camera Roll and the Mail application. But we can use URL schemes to accomplish much the same thing.

I worked with Fraser Speirs and Ian Baird during the development of Exposure and Cocktails so that their applications could support a “Post to Twitter” button. Clicking on that button initiates a workflow that lets the user share what they’re looking at on Flickr or what kind of drink they’re enjoying. Leaving their app/task and launching Twitterrific makes complete sense.

If you’d like to include a “Post to Twitter” button in your own application, all the code you need is in the postToTwitter: method in the very special application mentioned above. If you want to handle your own URL scheme, take a look at application:handleOpenURL: in the application delegate. Be careful about validating inputs: you don’t want malicious URLs to do bad things.

Not to dampen your enthusiasm, but please be aware of a couple of limitations with URL schemes. First, there is no way to know if a URL scheme is supported or not (rdar://problem/5726829). Currently, the best you can do is to performSelector:withObject:afterDelay: then openURL:. If the selector gets called you know that the URL failed to open. Also, be aware that deleting an application can sometimes leave the URL registration database in a state where it no longer recognizes a scheme (rdar://problem/6045562). This only happens when two applications support the same URL scheme, so you can avoid the problem by using a unique scheme name. Please use the Radar bug ID# for a “me too” bug report if this becomes a problem in your own application.

Now let’s enjoy our newfound freedom to discuss the iPhone SDK and the first of many sample code releases on this site!

Update October 1st, 2008: As Jonathan Rentzsch and Thomas Ptacek point out, URL schemes can be an attack vector for your application. Pay particular attention to the code in -application:handleOpenURL: in your application delegate. If you find any vulnerabilities in my code, please let me know so I can update this essay. Thanks!

Update October 7th, 2008: Once your application supports URL schemes, it’s likely that you’ll want to provide a bookmarklet in Safari. Here’s the one we use in Twitterrific:

javascript:window.location='twitterrific:///post?message='+escape(window.location)

The hardest part about doing this is guiding the user through the setup process. Joe Maller came up with a simple solution that lets the user get the Javascript into their bookmark list. This was later refined by Alexander Griekspoor. Make sure to view the source on these pages for additional hints and installation instructions.

If you agree that this setup process is too difficult for end users, please submit a duplicate bug for Radar ID# 5935641. Thanks!

Update February 4th, 2012: UIApplication now provides a -canOpenURL: method that lets you check if there is an application installed that supports the URL scheme.

Killing our enthusiasm

Dear Steve,

I am an iPhone developer. I love Cocoa Touch—it’s an amazing piece of engineering. I’m having great success with the products I’ve written (one of them even won an ADA at this year’s WWDC.) Sales through iTunes are great and well above my expectations.

And despite of all this, I’m feeling ambivalent about developing new applications for the iPhone.

Of greater concern is that I’m not alone: many of my colleagues are starting to feel the same way. To illustrate, here are some thoughts from people whose work I respect and admire:

Steven Frank – Panic (Transmit/Coda/Unison)

Fraser Speirs – Connected Flow (Exposure)

Wil Shipley – Delicious Monster (Delicious Library)

Brent Simmons – NewsGator (NetNewsWire)

You should also be aware that much of the discontent is being masked by the NDA that’s currently in place. I, and many others, do not want to anger Apple and there are no forums to voice our concerns privately.

As you’ve seen throughout your own career, great engineering is not driven solely by financial rewards. Woz didn’t write Integer BASIC by hand because he thought it would make him rich. Andy Hertzfeld’s and Bill Atkinson’s work on the original Mac wasn’t motivated by greed.

Great developers create amazing software for love as much as money. Take away the artistry and craftsmanship and you’re left with someone pumping out crapware for a weekly paycheck.

I have worked on many different platforms throughout the years: the most important benefit to working on the Mac is the vibrant community of developers. The high quality of Mac applications is due in large part to great developers being able to learn, compete, innovate, and share in a common success. This camaraderie sustains the love for the platform.

I was hoping the same would be true for the iPhone. Sadly, it’s not, and that makes this new platform really hard to love. I’m trying to stay positive in spite of recent developments, but I’m finding my attraction to the iPhone fades a little bit each day. I think it’s important that you know that.

Thanks for your time,

Craig Hockenberry

P.S. As I was writing this essay, Jason Snell and Dan Moren posted some articles at Macworld about the App Store and NDA. The disaffection is starting to spread outside the development community.

Update October 1st, 2008: Thank you.

Lights Off

There was a time when I would have never considered jailbreaking my iPhone. That was a time before I saw Lucas Newman’s and Adam Betts’ groundbreaking application for the iPhone: Lights Off.

It’s a simple game. It’s simple code. And it demonstrated what was possible for the rest of us outside of Cupertino. I was hooked. Big time. Seeing Lights Off at C4[1] was an inspiration for pretty much everything I’ve done on the iPhone since.

As a result, I feel compelled to document this historic piece of software. And what better way to do this than with source code that works on the iPhone 2.0 software. The archive also includes a Jailbreak folder that contains the original source code that worked with version 1.0 of the iPhone OS.

Do not look at this code for tips on how to design iPhone applications correctly. Rather, it is a testament to the curiosity and coding instincts that were required for developing sophisticated software without any documentation or headers. I purposely made as few changes as possible while porting to 2.0: FileMerge can be used to see what’s improved since Jailbreak and you’ll see plenty of compiler warnings.

Since Lights Off is inspired by Tiger Electronic’s game Lights Out, it doesn’t feel right to release this via the App Store. You will have to build and install it yourself; no exceptions. If requested, I will remove this project from the site, so get it now.

Now who will be the first to reach line 212 in puzzles.plist?

Note: If Apple feels this information is crossing over the NDA line, I’ll be removing this essay and the accompanying code. It’s probably a good idea to save it for reference. Hopefully they’ll realize that college students and other developers learning about the iPhone will find it helpful.

Dealing with memory loss: the cleanup

As we saw in the previous post, your view controller’s view can be released at any time because the device needs memory. One of the things you’ll want to look at in your own code is how you cleanup when when the memory warning occurs.

Here’s an interface for a simple view controller class that embeds a web view. The web view is retained because our controller will need to send it messages:

@interface IFWebViewController : UIViewController
{
  UIWebView *webView;
}

// webView is retained because we'll want to do things like this:
//   [self.webView loadRequest:[NSURLRequest requestWithURL:newUrl]]
@property (nonatomic, retain) UIWebView *webView;

@end

We setup and retain the web view when the controller’s loadView method is called:

- (void)loadView
{
  ...

  UIView *contentView = [[[UIView alloc] initWithFrame:applicationFrame] autorelease];
  self.webView = [[[UIWebView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, applicationFrame.size.width, applicationFrame.size.height - 44.0f)] autorelease];
  [self.webView setScalesPageToFit:YES];
  [self.webView setDelegate:self];
  [contentView addSubview:self.webView];

  self.view = contentView;
}

Eventually this method will be called:

- (void)didReceiveMemoryWarning
{
  // default behavior is to release the view if it doesn't have a superview.

  // remember to clean up anything outside of this view's scope, such as
  // data cached in the class instance and other global data.
  [super didReceiveMemoryWarning];
}

The view and its sub-views are automatically released and set to nil by the default implementation. But there’s a problem here: the primary consumer of memory in this view has not been released. The web view is still being retained by the controller.

Since UIViewController doesn’t notify you that the view is being freed, you’re left holding onto a web view that you can’t do anything with since it’s not a part of the view hierarchy.

You can’t solve this problem by using a non-retained reference to the view because you’ll be left with stale reference and a crash if you try to use it. Instead, you’ll want to override the method used to set the view in UIViewController:

- (void)releaseSubviews
{
  self.webView = nil;
}

- (void)setView:(UIView *)view
{
  if (view == nil)
  {
    NSLog(@"setView: releasing subviews");
    [self releaseSubviews];
  }
  [super setView:view];
}

Of course, you’ll also want to release the retained views when the controller is freed:

- (void)dealloc
{
  [self releaseSubviews];
  [super dealloc];
}

When you’re debugging this code, make sure to force the memory warnings in the simulator. You can do this easily with the Hardware > Simulate Memory Warning menu item.

I’ve also found it helpful to poke around in the view controllers instance variables to see what’s allocated, check retain counts, etc. Since you can’t access self.view or [self.view superview] without instantiating a new view instance, you’ll need get at the private property using KVC:

- (void)didReceiveMemoryWarning
{
  // use this to look at view (without creating an instance with self.view)
  NSLog(@"didReceiveMemoryWarning: view = %@, superview = %@", [self valueForKey:@"_view"], [[self valueForKey:@"_view"] superview]);

  [super didReceiveMemoryWarning];
}
(Do not be an idiot and use this technique in production code: there’s nothing to prevent the iPhone SDK developers from renaming this property and causing your app to crash.)
When debugging, it’s also convenient to know when the view is instantiated (or re-instantiated.) Some logging code in viewDidLoad does the trick:
- (void)viewDidLoad
{
  NSLog(@"viewDidLoad: view = %@", self.view);
}

A lot of the complexity in this memory cleanup could be mitigated by UIViewController supporting a unloadView that is called prior to setView:nil in the default didReceiveMemoryWarning. If you agree, feel free to submit a duplicate bug on Radar ID# 5834347.

At present, there are no metrics for memory usage on the device, so there’s a bit of guesswork in determining how much of your view controller’s object graph should be released. If in doubt, free your data and let it be re-instantiated lazily. You’ll find the overall user experience to be much better.

Note: If Apple feels that sharing this information is outside of the bounds of the NDA, I’ll be forced to remove this post. So just add this to your ever growing collection of furbo PDF files.