How to create offline webapps on the iPhone

Recently Google launched their latest mobile version of Gmail optimised for iPhone and Android based browsers. One of the features that stood out was the offline access thanks to the browsers support of html5 application cache.

Documentation on the application cache feature supported in safari iPhone 2.1+ is scarce and of that documentation it doesn’t go into great detail. The best place to learn about this is on the Safari DevCenter under the mobile section, there it has 2 documents introducing the user to offline webapps; the first is a quick rundown on just the manifest file and the second article touches on a few more features available to offline webapps such as the javascript events for updating the cache when the user is online. We’ll delve into these later in the article but first let’s take a look at a working example.

Update: Added additional information about the event summary and the order in which events get executed. Added link to useful tool for sniffing file Content-Types.

To see the offline webapp in action, load the demo on your iPhone, then turn Airplane Mode on, re-open Safari and reload the demo. This time it will fetch the files from the cache that was created on the initial load.

How does it all work?

To get a basic offline webapp up and running is incredibly simple but it does have one caveat which tends to get a lot of people frustrated quickly when for some reason the offline feature isn’t working. We’ll explain that issue shortly.

The first thing we need to do is create what is called a cache manifest file which has references to all the resources we want to save. Whether it be JavaScript, CSS, images or html, below we can view the manifest file used in the demo.

CACHE MANIFEST
 
# Offline cache v1
# html files
article.html
 
# css files
assets/_styles.css
 
# js files
assets/_javascript.js
 
# images
assets/ico_ninja-star.gif

The manifest file is a simple plain text file that holds the file names you wish to cache, the paths are relevant to where you saved the manifest file which is recommended to be in the root of the site. This file will be saved as filename.appcache, where filename is anything you wish. Lines that start with a # are comments and are ignored by the cache.

Referencing the cache file

We now have our cache manifest file setup and named, the next thing we need to do is reference that file in the html so this offline access will function for the specified files in the manifest.

<html manifest="thecssninja.appcache">

The manifest file is referenced in the html tag through the manifest attribute and just points to the filename.

Now the one issue that catches a lot of people when trying this out is not setting up the mime type correctly in their chosen web server. Whether it be IIS, Apache or something else, you’ll need to make sure that the .appcache file is fed through as text/cache-manifest.

For Apache you can add the text/cache-manifest to the mime.types file found in the conf folder in apache and add the following to the bottom of the file.

# html 5 application cache - offline access
text/cache-manifest    appcache

For IIS open up the IIS manager found in Control Panel > Administrative Tools > Internet Information Services double click the Mime Types icon in the manager and scroll down the list until you find the .appcache file and edit the mime type to have text/cache-manifest.

IIS 7.0 Manager Mime Types

That’s it, to get a basic offline webapp to work is incredibly simple all it takes is the manifest file to specify which files to cache, declare it in on the <html> tag using the manifest attribute and you have yourself a site still accessible even if the user has no network connection.

UPDATE: GeoNomad in the comments suggested a great tool to make sure your manifest file is being fed through with the correct mime type, Web Sniffer. I put my manifest file through the Web Sniffer tool, you can see that the Content-Type is set as text/cache-manifest.

Updating the cache

If you want to update the cache you either need to change/add file references to the manifest and then tell the cache to update. Updating a file that the manifest calls will not make the cache reflect the changes. The mozilla developer article on applicationCache makes a good suggestion to version your manifest file as the JavaScript events described below will only fire if there is a change to the manifest file. To update the cache we have:

window.applicationCache;

This object has various stages depending on what the cache is doing which can be checked by using the .status method. There are 6 different stages:

  • Status 0 (UNCACHED) is returned which means that there is no cache available
  • Status 1 (IDLE) is returned means the cache you have is currently the most up-to-date
  • Status 2 (CHECKING) is returned means there is a change in your manifest file and it is checking it for changes
  • Status 3 (DOWNLOADING) is retuned means changes have been found and they are being added to your cache
  • Status 4 (UPDATEREADY) is retuned means your new cache is ready to be updated and override your current cache
  • Status 5 (OBSOLETE) is returned means your cache is no longer valid meaning it has been removed

These available statuses can be attached to the applicationCache object with event listeners so we can tell the web app what to do on what status. In our case we want to update the cache to reflect changes we have done.

var webappCache = window.applicationCache;

function updateCache() {
    webappCache.swapCache();
}

webappCache.addEventListener("updateready", updateCache, false);

In the above example we attach an event listener to the applicationCache looking for the updateready status which means there is a change to the manifest file and our cache is ready to be updated we do so by using the swapCache() method.

There is 2 more event handlers which do not appear in the window.applicationCache.status these are part of the event summary. Those are error & progress. We’ll take a look at how to use the error event, the progress event is basically the same as the downloading event handler.

var webappCache = window.applicationCache;

function errorCache() {
    alert("Cache failed to update");
}

webappCache.addEventListener("error", errorCache, false);

All the error event handler does is return the specified function we attached in the event listener when the cache is not available or does not exist.

Event summary

In the applicationCache we have various events available so when the cache is being updated, or as mentioned above when something goes wrong. We can attach to those events and keep the user informed on what is happening. These events will execute in specific order based on what is happening with the cache and therefore give us useful hooks to represent that back to the user.

Pete who commented below and needed a way of indicating to the user that the cache was downloading prompted me to look further into the events that get fired and which ones would be useful in this scenario. I created a demo which will indicate to the user that the cache is downloading by showing a loading icon and once the cache had finished downloading, the loader would be hidden.

The event order for this example was as follows:

  • checking – The page loads and the cache is checked for any changes.
  • progress – The download event actually gets fired before this but we don’t need to hook into that. The progress event will fire for each file that is referenced in your manifest until the cache has finished downloading.
  • cached – The cache has successfully downloaded and the cached event is fired so we utilise that to hide the loader.
  • updateready – This event is fired so we can then swap the old cache with the newly downloaded one.

In the above mentioned demo we also attached event listeners to the error and noupdate events so we can handle errors and if the cache is already up-to-date.

Is the user online

There is also a new method on the navigator object called onLine which returns a boolean value if there is a network connection or not.

navigator.onLine

In the article example the onLine method is used to update the title if the user is on/offline.

Only the beginning

The applicationCache is not yet finalised and is of course subject to change with newer and refined features. I’m sure there will be additions in the upcoming iPhone OS 3.0 release. If there are updates unfortunately these cannot be discussed until the NDA is lifted. But of course this won’t stop us speculating.

Some of the features available in applicationCache on firefox 3.1 and safari 4 will inevitably be added in future releases of the iPhone OS. Such as the NETWORK: and FALLBACK: sections in the manifest file, these allow for the developer to specify a file they wish to never cache or to have a file load if something fails with the first option.

Short sharp facts about the appcache: http://appcachefacts.info/

Post filed under: css, javascript, xhtml.

Skip to comment form.

  1. Ryan Seddon says:

    @Rodrigo A

    Your manifest file has the wrong mimetype web-sniffer is telling me it’s application/octet-stream.

  2. Jorge says:

    I am having lots of problems with this, so if you don’t mind I would like to ask you a few questions. I have checked everything, but my real scenario is complicated, so I have tried to upload your project and it is not working for me:

    http://www.portfoliomultimedia.es/apps/offline_webapp

    http://www.portfoliomultimedia.es/apps/offline_webapp/thecssninja.manifest

    The only thing I have changed is the .manifest, because it is the myme termination I added.

    So, my questions would be:

    - Is there something more to check in the server than the myme type?

    - All the files loaded by the initial index must be in the manifest?

    - If I have a video added to the manifest it fails or it just doesn’t cache it?

    - Are you all sure about the 5mb limit?. I have been trying and think that I have seen a dialog asking me to let the app grow the mb limit.

    - Is there any issue related to the app being in fullscreen mode?

    Thank you very much for this great tutorial

    Jorge

  3. Ryan Seddon says:

    @Jorge

    Your hosted demo is missing a reference to the vomzom.svg file, if any file is missing that’s declared in the manifest it will fail.

    All the files loaded by the initial index must be in the manifest?

    No, just the files you wish to cache so they’re available offline.

    If I have a video added to the manifest it fails or it just doesn’t cache it?

    I believe iOS5 may allow videos and audio to be cached offline, before that it didn’t work.

    Are you all sure about the 5mb limit?. I have been trying and think that I have seen a dialog asking me to let the app grow the mb limit.

    I’m not a 100% sure this is what other people have found to be the case, there may very well be a prompt to increase storage size.

    Is there any issue related to the app being in fullscreen mode?

    Fullscreen mode should be fine, if it’s a UIWebView inside an app offline caching is not available.

  4. Gerry Straathof says:

    The message that comes up when you visit a cached site is to increase the cache storage of your device, not the individual site. I’ve had.mine go up to 50M when doing beta testing. if you installed the Kindle reader you will also get a confirmation.

    The individual site allowance is above 5M on ios5, but i am not sure how much more. the highest mine has gone is 9.75M for a website magazine that was part of a class project.

    I use a php file which builds a manifest file for a site using everything in the folder. it has made it much easier to control changing documents.

    I think the kindle reader and new York times(?) use sql to hold their downloaded data, but have notdirected their codes behaviour with chrome or safari audits…

  5. Jmv says:

    Hi Mr Ninja
    Thanks a lot for all your great work and help!
    I ve installed a launch screen to your demo app on my iPad (ios4.2.1)
    I have the very same problem as many people above: works fine online/offline but if I 1/ go offline 2/suppress the screen in safari , then I get the message ” cannot acess bla bla bla… ” when i restart the webapp, which makes it not very usefull…
    Unless I missed a line, I think you answer has always been up to now: ” i don’t have this problem with my IOS version”. Is it still the case or have you experienced the problem and found a solution? what ios version do you use?
    Thank in advance for you enlightments.

  6. Jmv says:

    Hi Mr Ninja
    (previous post continued)
    I have done a lot of tests with your files on my website (jmv38.comze.com). Thanks to your console-data-logger I have found the following strange facts, please comment if this sounds correct to you:
    - your code makes an offline app from the file ”index.html”, but on the ipad/safari I can access to the offline page only by calling ”jmv38.comze.com”, and not ”jmv38.comze.com/index.html” that returns an ”no connection” error.
    - then the call back to ”index.html” from ”article.html” never works offline. Putting href=”jmv38.comze.com” doesnt work either because it seems to call ”jmv38.comze.com/index.html”…
    So I thought I was now ready to make of my own page (a game timer) a webapp. I have done everything as in your code (and copied your console-data-logger with minor modifications), and… ‘:-(( it doesn’t work! The cache apparently downloads the files, but at the end of the process an error is fired. If I put 1 or 2 html files only in the cache the errors fires much faster. I have used the sniffer to verify that my manifest has the correct mime-type: ok. So I have no idea of what is happening… Could you give a look to the problem? Your help would be very very very much appreciated! Thanks in advance!
    Jmv

  7. Brett says:

    This totally kicked ass! Thanks for the great article.

    I just wanted to share that the article.html page links back to

    http://www.thecssninja.com/demo/offline_webapp/index.html

    instead of just http://www.thecssninja.com/demo/offline_webapp/

    which causes a “Cannot Open Page” error; no doubt because it thinks that “index.html” is different to “/”. Changing the url back to

    http://www.thecssninja.com/demo/offline_webapp/

    fixes the problem. you might want to correct this in the link itself :)

    thanks again!

  8. Jmv says:

    Hi Mr Ninja, it’s me again.
    I have done some more tests I’d like to share with you.
    I have put your code in a new domain name, and everything worked very fine online, offline, all the console message were OK.
    Then I have added a single line in the 2 html files:

    <meta content="yes" name="apple-mobile-web-app-capable">
    

    This is to make the app open like a stand-alone app, not in SAFARI.
    And then everything went wrong: always an error fired after downloading of the cache.
    So I suppressed the line, changed the cache revision so it updates the files, but it never worked back again… My safari seems to be stuck in a ”problem state” from where it won’t go out. Don’t know what to do…
    Any advice?
    Best regards.

  9. Ryan Seddon says:

    @Jmv

    I just tested your web app in firefox, chrome and iOS5 safari, they all work offline and the caching works correctly. I can’t see any problems with your code of manifest file (although your manifest file should now have the extension .appcache over .manifest, but that shouldn’t effect anything).

  10. Ryan Seddon says:

    @Brett -

    Thank you! I’ve updated the demo and demo files to add “index.html” to the manifest file so now that link back to “index.html” will work offline correctly.

  11. Doug says:

    Thanks for the post. Very helpful. When I read about the Mime type I thought “That’s why it’s not working!” but alas things are still quirky.
    Using an iPad2:
    1. I visite your demo page online
    2. I then go offline and visit the demo page again by entering http://www.thecss... in the navigation bar. I select the demo page from the drop down and the demo webapp comes up fine.
    3. Hit refresh; Safari come up with message. Cannot Open Page: Safari cannot open the page because it is not connected to the internet.
    4. If I enter the whole address in the bar and click ‘Go’ I get the same error message.
    5. If I make a bookmark and click it offline I will get your webpage without the error message.

    My application on the otherhand will only continue to work after being disconnected if I do not navigate away from the page. I can’t bring the page back up with out getting the error message when I have disconnected from the network.

    Is there a setting that will instruct safari to look for a cached website before it tries to connect to the internet? That seems to be the issue.

    Thanks, Doug

  12. Jmv says:

    @Ryan Seddon
    (previous post continued)

    Hi Mr Ninja

    Thank you very much for having looked to my problem.
    So for you everything was ok and for me there was an error.
    I have tried again my own webbapp after not trying for one week and..
    It worked! No error, everything fine! Even as a stand-alone webbapp (with a launch screen and not opening in Safari). Why is it so unpredictable?

    I have found an excellent site that give some explaination of what might be happening here. In short: it has to do with the fact that files send by the web server to safari have themselves some ”lifetime” and are not resend systematically, even if they have changed. Another ”cache” is involved here, the native browser cache, that is different from the ”appCache” we are trying to put in place. So although we change our file on the server side, it may not be changed on the iPad-Safari side, at least for some time, depending on how the lifetime is parametered on the server side! The site gives some advice on how to put some more commands on the .htaccess file to makes sure files are resent each time they are requested on the client side. I haved tried these commands, and also forced the safari cache to empty, but since everything was fine it has not changed anything.

    This excellent site is: http://diveintohtml5.info/offline.html , see the chapter named: THE FINE ART OF DEBUGGING, A.K.A. “KILL ME! KILL ME NOW!”

    The file refresh problem I just described might well be the cause of the problems many other users have experienced, driving them nuts! So I hope this post will help them.

    So now i should be happy with my webapp… but not quite. I have implemented some sound effects in an .mp3 file: they work fine online but not offline. Do you have any idea of what might be going on? Maybe caching mp3 is not allowed on ios 4.3? I have found no clear answer on the web.

    Any help on the subject would be highly appreciated

    Thanks
    JMV

  13. Prodyot says:

    I thank God (if He exists) and or thank CSS Ninja for not closing comments to this post :)
    Awesome tutorial and awesome advances in HTML5.
    Thanks, O’ CSS Ninja.

  14. Mauricio says:

    hello! i’m reading a book called build web apps for iphone with html/javascript and i’m stuck in an error…

    basically, i incremented the offline style for my webapp… i didnt create the htaccess file cause in the book said there was another way…

    in the html tag i used

    and in my manifest.php is like:
    IsFile() &&
    $file != “./manifest.php” &&
    substr($file->getFilename(), 0, 1) != “.”)
    {
    echo $file . “\n”;
    $hashes .= md5_file($file);
    }
    }
    echo “# Hash: ” . md5($hashes) . “\n”;
    ?>

    so that way i get all folders dinamically. my problem is:

    everytime when i load the page for the first time, it downloads all the necessary folders files for my application to run offline. and loads the page normally, with all funcionalities working

    but whenever i RELOAD the page, it simply cant get my files.js and .css..
    i’m debugging using safari… and i have no ideia of why is this happening, and in the book dont mention that.

    do you know any possibility of why is that happening???

    (sorry for the bad english, i’m from brazil)

  15. Geddy Straathof says:

    The first thing that comes to mind is not having an expiry date set in your htaccess file for .js and .css prefixes… Without a longer expiry date it will have to reload those.

    Creating the .htaccess file is different than creating the manifest file. They do different things.

  16. Ey men, I am trying to make an html5 app for ipad and needs an offline video. I tryed, studied documentation and googled, but cant find if its posible to have offline html5 video in an ipad!

    All the best,

    Alex

  17. Geddy Straathof says:

    This would really depend on the size of your video. I would assume the entire website, city video, would be too much for the limitations on whatever device you are viewing it on (usually ten Meg)

  18. Jmv says:

    Hi Mr Ninja

    Now i can access the page again, thanks.
    This post is a follow up of my previous ones (see above). I have 2 news to share concerning cache with IOS.
    1/ I finally upgraded to IOS5: now my webapp works 100% offline, including the sounds, as you had said. So for IOS4.x users: upgrading is a must if you want to make a functionnal webapp with sounds.
    2/ For sounds I have read the same problems in all discussions: IOS will work and cache only 1 sound: if you try to use several files, it gets lost and works an awkward way. Because I need several sounds in my game, I have done as some others: I have concatenated all my sounds in 1 single mp3 file. To use a given sound that start at t0, I use the javascript functions to start the reading at a given time t0 and I stop it after a delay d0 corresponding to the sound duration. This works fine.

    Hope this helps someone.

  19. Diane says:

    Thank you for the information. I was looking for this for a long time

  20. Yoona says:

    What to do if my site is gallery like consisting of a hundred of images and I want it to be accessible offline (so users could browse through my gallery) and on mobile (iPhone). Caching all these images, wouldn’t this be heavy for a mobile? What do you suggest having this kind of setup? or it’s just the way it is and will work fine? Please help me. Thank you.

  21. Ryan Seddon says:

    @Yoona -

    You’d probably hit the appcache limit which is 25mb and trying to download 100s of images is a bad idea. I’d probably try and offline cache the thumbnails and then have messaging when users are offline to say higher resolution images are only available when online.

  22. This can be done several ways depending on your goal. For example, you can have the images as separate files, which can take a while to load and store. You can also do what the iOS photo app does, which is create a large file of thumbnails, five or so wide and rather long (maybe not as long as the iPhoto one)

    You can then display only one portion of the image… This may not be recommended for larger images, storing a series in a strip, but you can try it for yourself.

    You can also optimize the images so they are resized appropriately for the device a user is browsing on.

    I have successfully stored twenty five large images (full width/height) but I find it takes longer for the device to juggle things before it allows you to save it for offline reading. (You can’t visit/save… It’s more visit, wait a bit, save)

    If you have a large portfolio with a huge number of images you may want to make several smaller dedicated portfolios.

    Individually I have found images that are less that 160k works well and fewer than twenty or so.

    If you absolutely require a huge number of images, try to remember the patience of the visitor. While you are okay with the longer download for a huge portfolio, I have found others to be less willing to wait.

  23. Ryan Seddon says:

    Thanks Gry good insights!

Leave a comment