iPhone offline web applications: tips and gotchas

January 24th, 2010 § Comments Off on iPhone offline web applications: tips and gotchas § permalink

I spent this evening updating my “iPhone VR” Javascript/CSS demo to work with iPhone OS 3.0 (it had stopped working 100% correctly since the OS update). I also decided to spend some time making it work as an offline-capable web application.

The basic process of making your web app cacheable offline is, in theory, fairly straightforward, and generally well-documented at Apple’s website. I ran into some interesting headaches though.


  • First thing to note is that your web server must serve your cache manifest file with a MIME type of text/cache-manifest. This may mean editing mime.types to add a line that looks like this:
    text/cache-manifest   manifest

    or perhaps a line in httpd.conf that looks like this:

    AddType text/cache-manifest  .manifest
  • The next thing that had me stumped for a while: while your web server may know how to take the URL http://example.com/webapp/ and automatically and invisibly serve up the file http://example.com/webapp/index.html, your offline web app knows nothing of this mapping of webapp/ to webapp/index.html. If the URL bar reads http://example.com/webapp/ and you save it locally using a home screen bookmark, it will fail to launch correctly if the device is offline, even if index.html file has been cached. The device simply does not know to look for webapp/index.html instead of webapp/. Thus, you must ensure that the URL bar reads webapp/index.html before the user makes a home screen bookmark.
  • At first, I thought I would enforce the above with a tiny bit of JavaScript that simply reads window.location and redirects to window.location + "index.html" if the URL ends in a slash. This worked while online, but it broke my app when offline, even when the redirection was not taken. Why? It seems that any reference to window.location in your script is treated as network access. Since the device is offline, it generates an error alert.

    Instead, I made my app live at webapp/main.html, and created a small webapp/index.html file that simply redirects to main.html. (I could have used a server redirect inside of an .htaccess file instead, but I chose not to, for no particular reason.)


  • You can specify a custom icon for the home screen bookmark using a <link> element, like so:
    <link rel="apple-touch-icon" href="custom_icon.png" />

    If you don’t want iPhone to automatically apply the “shine” effect on your icon, use the following instead:

    <link rel="apple-touch-icon-precomposed" href="custom_icon.png" />
  • Since iPhone OS 3.0, offline web apps can have custom splash screens, just a like a native app! Simply add a link element to your web page:
    <link rel="apple-touch-startup-image" href="/splash.png" />

If you want to see the results of all this, go to http://bunnyherolabs.com/iphone/xform/ on your iPhone or iPod Touch, then click the “+” (add bookmark) button and choose “Add to Home Screen.” Now you can click on the “Panorama” icon on your home screen to see the demo at any time, even when not connected to the internet.

How universal is “universal”?

January 1st, 2010 § Comments Off on How universal is “universal”? § permalink

Tip: if building a +universal variant in MacPorts (for example, because you’re trying to build and install the Python Imaging Library), then you should check /opt/local/etc/macports/macports.conf to see what MacPorts considers “Universal”:


The machine architectures to use for +universal variant (multiple entries must be space delimited). Options include: ppc, i386, ppc64, x86_64

Default: x86_64 i386 (ppc i386 for 10.5 and earlier)

(from http://guide.macports.org/#internals.configuration-files)

On Mac OS X 10.6 (Snow Leopard), the default value for universal_archs is x86_64 i386. Note that it does not include the PowerPC architecture. This makes perfect sense, because Snow Leopard doesn’t run on PowerPCs. Unfortunately, when installing PIL with pip, it builds for PPC as well, and thus requires PPC architectures in its dependent libraries, even on 10.6. I do not know how to disable PPC support in PIL (or pip?). All of this Python extension building stuff is new to me :)

The upshot: if you are running 10.6, then you must edit macports.conf and add “ppc” to universal_archs before you follow the directions in the linked article.

Easter egg in your iPhone app? Don’t hide it from Apple

October 21st, 2009 § Comments Off on Easter egg in your iPhone app? Don’t hide it from Apple § permalink

Yesterday, when I logged into the iPhone Dev Centre, I was greeted with a “please agree to the new developer agreement” alert. As usual, I copied the text and diffed it with the previous version. The bulk of the changes had to do with allowing in-app purchases in free apps.

But the following change caught my eye: in section 12.2, “Termination”:

12.2 Termination
This Agreement and all rights and licenses granted by Apple hereunder and any services provided hereunder will terminate, effective immediately upon notice from Apple:

(f) if You engage, or encourage others to engage, in any misleading, fraudulent, improper, unlawful or dishonest act relating to this Agreement, including, but not limited to, misrepresenting the nature of Your submitted Application (e.g., hiding or trying to hide functionality from Appleā€™s review).

(emphasis added)

So be careful, hidden-unlockable-feature-mongers. Apple could terminate your licence!

Genesis^H^H^H^H^H^H^H Droid Does

October 18th, 2009 § Comments Off on Genesis^H^H^H^H^H^H^H Droid Does § permalink

Verizon’s hard-hitting “Droid Does” anti-iPhone ad (via TechCrunch):

I love my iPhone (and developing for it) but I definitely would like to see more competition in this space. I think this ad hits home for those of us who are frustrated with the iPhone’s shortcomings. On the other hand, the style of the end of the video makes me think of trailers for horror movies. I’m not sure that’s such a good association to make!

This ad campaign also reminds me of the old Sega Genesis commercials back in the day:

Flash and iPhone

October 11th, 2009 § 1 comment § permalink

It’s been a few days now since Adobe announced that Flash CS5 Professional will have a “publish for iPhone” feature for ActionScript 3 projects. Just wanted to jot down a few thoughts:

As a Flash developer and geek, the technology seems pretty damn impressive to me. It actually includes the LLVM compiler? Wild.

Still, there are technical concerns, although to be fair, there are many months before CS5 ships. And for the moment, Flash-built apps won’t have access to things like the iPhone’s native UIKit controls, but they will have access to the accelerometer and multitouch (which at first I thought they did not).

Furthermore, as an iPhone developer, I have concerns, and in a way these concerns have less to do with Adobe’s actions than Apple’s: the single chokepoint that is the App Store and its review/approval system. It’s clear that Apple’s review system does not scale (longer and longer delays in approvals), and discoverability is bad enough as it is with the number of apps in the store now and the limited number of ways there are to browse and find things in the store.

If the iPhone app ecosystem was completely open, with many “stores” and multiple ways of finding and buying apps, I’d welcome Flash-built iPhone apps with open arms: the more the merrier. As it is, though, I worry a bit about the flood of muck as every Flash developer (over a million by Adobe’s count: A MILLION!) with a back catalogue of content tries to get their old code into the App Store.

Some obvious predictions:

  • We’ll see more than a few Flash component libraries that emulate UIKit controls
  • Apple will unofficially delay or reject Flash-built apps for the first while until/unless Adobe and Apple come to some kind of understanding (see the issues that PhoneGap apps have had in the past, and that uses all native SDKs!).
  • Flash developers will find it more difficult than they expect to get their old code working well on the iPhone
  • Many iPhone programming contracts will be lost as clients decide (correctly or incorrectly) that they can do their iPhone project in-house with Flash

Still, I have to admit I personally can’t wait to get my hands on the public beta of Flash CS5. I enjoy working with Flash and ActionScript.

Twitter follower tracker

September 18th, 2009 § 1 comment § permalink

There are a number of services out there that are supposed to notify you of when people follow or unfollow you on Twitter. I’ve tried a few and most of them seem mostly broken. The best one out there, formerly at joule.marnanel.org, is currently down and its creator doesn’t know when it’s coming back, alas.

The problem with running a service like that is scaling when huge numbers of people join. It always struck me that something like this might be better handled on the client side. It’s such a conceptually simple operation (fetch followers list, do a diff with the last time you checked), and each person/computer only has to worry about their own accounts. I’m surprised that Twitter clients don’t have functionality for this built in (that I know of).

I’ve been wanting to play with the Twitter API for a while, and I’ve also wanted to delve a little bit into some of the newer features that modern web browsers have to offer, so I’ve created a simple Twitter follower tracker proof of concept in JavaScript.

Twitter follower tracker in action
It’s a very crude app. It doesn’t update automatically—you have to manually refresh the data when you want to check to see if your follower list has changed. Also, the new and lost followers are shown only with their numeric user IDs. Each ID is a link, however—clicking on the link will fetch that user’s information and convert the number into a username. Clicking again will take you to that user’s profile. I did this to minimize the quantity of Twitter API calls.

Yes, the page is hosted on my site, but all of the work is done by the browser. It uses jQuery to fetch the data from Twitter; thanks to Twitter’s handy “callback” parameter in JSON responses, I don’t have to worry about having a proxy on my server handle any cross-domain requests. I use the localStorage property, supported in some modern browsers, to remember the user’s follower history. LocalStorage is interesting and easy to use. It’s like a giant browser cookie, except that its contents don’t get sent to the server.

Caveats: it’s just a proof of concept. I’ve only tested it (minimally) on a couple of browsers, on Mac OS X. It doesn’t do any error checking. I’m sure it will break if you have a lot of followers. I’m sure it may break if you look at it funny. It may well have security issues. I’m not sure what the storage limits are for localStorage, but if you hit the limit, I have no idea what will happen. Since localStorage is in the browser, you won’t be able to see your follower history if you move to a different machine or even to a different browser on the same machine.

If you want to look under the hood, all the real work is done in follow.js. It’s not well-commented at this point—it’s kind of a mess. I might put it under some sort of open source licence, although I’m not really sure if it’s worth it for something so simple.

Give it a shot and let me know what you think :)

Twitter follower tracker.

Another UITabBarController change from 2.x to 3.0

July 22nd, 2009 § Comments Off on Another UITabBarController change from 2.x to 3.0 § permalink

Or actually with UITabBarControllerDelegate, and specifically, with the method tabBarController:didSelectViewController:.

According to the docs, the differences:

OS called only when
tab changes?
called when changed
before OS 3.0 YES YES
OS 3.0 and later NO NO

If you are writing code that runs on both 2.x and 3.0 that needs to get tricky with tabs, these changes are a nuisance.

Even more infuriating is that the Apple documentation isn’t complete. The section ends with the sentence fragment “If you are implementing….” Yes? If I am implementing what? And what do I do if I am?

Yes, I’ve already reported documentation error. No response though. My guess is they’ll just remove the sentence fragment instead of expanding on it :(

[edited: I made a mistake in the table the first time I posted this. Should be fixed now.]

I’ve never gotten this to work

July 17th, 2009 § Comments Off on I’ve never gotten this to work § permalink

Troubleshooting problems with WordPress’s automatic upgrade feature would be a lot easier if it gave more detailed error messages than this:
unhelpful wordpress error message

I keep thinking about moving away from WordPress anyway…


July 11th, 2009 § Comments Off on Approved! § permalink

I forgot to mention that my apps (two versions of the same app: a free, ad-supported one, and a 99-cent ad-free version) were approved this past week on the iPhone App Store!

Please check out My Monkey.

Time from submission to approval: 13 days.

It’s a simple app, an Objective-C port of my Flash monkey widget. I added support for multitouch, the accelerometer and custom image backgrounds chosen from the user’s photo library.

It was a pretty straightforward port. The main thing I had to get used to again (coming from ActionScript) was the lack of automatic garbage collection.

I don’t expect this app to tear up the charts or anything like that :) but mainly as an exercise in learning iPhone programming and the whole process from code to distribution.

UITabBarController timing change from OS 2.X to 3.0

June 29th, 2009 § 1 comment § permalink

This is just a short note about a change I discovered between iPhone OS 2.x and 3.0. It’s not very interesting or exciting but I thought I’d record it in the off chance that it helps someone else.

When the user switches tabs on a UITabBarController in iPhone OS 2.x, the sequence of events is:

  1. UIView of new tab gets added as a subview
  2. UIView of old tab gets removed
  3. The delegate’s tabBarController:didSelectViewController: method is called

Run the same code on iPhone OS 3.0, however, and the sequence is this:

  1. UIView of new tab gets added as a subview
  2. The delegate’s tabBarController:didSelectViewController: method is called
  3. UIView of old tab gets removed (sometime later, I think it’s invoked via one of NSObject‘s performSelector: methods)

This is a inconsequential change for most, but it might make a difference if you are checking the UIViews’ superview variables in your tab bar controller delegate method.