How to Deploy an iPhone Web Application
Tuesday, December 23rd, 2008This article shows you how to deploy a stand-alone iPhone web application. In the process, we’ll also create a basic installer for any iPhone web application. To provide a working example, we’ll adapt a variation of the RedGreenYellowBlue web app described in the earlier article Building a Simple iPhone Web Application - RedGreenYellowBlue. We’ll turn our new program, rg_rotate, into a self-reliant, stand-alone application and also empower it with its own icon on the iPhone home screen. Once installed, the program can be run off-line, independently of any network connection or server-side resources. rg_rotate will show only two colors (red/green) but will conveniently change color when the iPhone is merely rotated. In portrait orientation the iPhone will shine red and in landscape it’ll show green. Tapping the screen is no longer needed to switch color.
Getting Started
To make an iPhone web application stand-alone, we need to provide up-front all the resources required for it to run independently. This means that any images, stylesheets, and scripts that are part of the structure of the program must be included in the program at the time it is installed. The easiest way to accomplish this is to convert any external URL references in img, script, link and other HTML tags into self-contained data URLs. Data URLs are a way for tags to embed their own data directly. A data URL is used as the value of a reference or location attribute within a tag. For example, an ordinary image tag <img src="some.gif"> could be transformed into something like
<img src="data:image/gif;base64,iVBORw0KGgoAAAANSUhEUg...">
The source attribute of the img tag now holds a base 64 encoding of the GIF along with a simple descriptor which specifies the protocol identifier (”data”), the appropriate mime type and subtype (”image/gif”) and the encoding method (”base64″).
All of the resources required by a stand-alone iPhone web application are encoded as data URLs. The web application is also encoded as a data URL. The encoded program is just a string of numbers and letters but it is far too long to type into the URL Bar of Mobile Safari. The challenge is how to provide the encoded program conveniently to the iPhone. The HTML viewer within the iPhone’s Mail application will not resolve a data URL, so the program cannot simply be e-mailed in a link to the iPhone. Mobile Safari, however, fully supports data URLs. A straightforward solution therefore is to provision an HTML page with a link that contains the complete, encoded program. This HTML page can be dynamically generated or provided statically. A dynamic strategy is quite flexible and can easily support an interface for uploading associated resources such as scripts, icons, buttons, and HTML fragments. A generator could then assemble the parts and return a complete, encoded result in a page for Safari to load as a bookmark. Once loaded into a bookmark, the stand-alone program executes independently from where it was initially installed as well as independently of any specific network connection.
Our Approach
For the sake of simplicity, however, our approach in this article will be to build a static HTML page that incorporates the target web application program source and the resources it requires. In our simple example, we will supply two external files: an image file that will be used as the home screen icon and the JavaScript/HTML web application itself. Both files will be converted to data URLs. First, the image file will be encoded into base 64 and embedded into the web application within a link tag. Then, perhaps surprisingly, the web application will be converted to a data URL as well. The data URL of the encoded program is incorporated into a provisioning web page which will be provided to Mobile Safari running on the iPhone. Any web server that is accessible to your iPhone can host the static HTML provisioning page needed by Safari. Mobile Safari can then be instructed to install your program into a bookmark along with its embedded home screen icon. Note that this solution avoids depending on a proprietary application like iTunes to synchronize bookmarks.
Installation Steps
In detail, the steps we will accomplish are as follows.
-
Identify files that need to be encoded into base 64. In our example, these are
rg_rotate_touch_icon.png, containing theapple-touch-iconshortcut icon andrg_rotate.html, containing the JavaScript/HTML web application we want to provision. -
Encode
rg_rotate_touch_icon.pnginto a base 64 representation. This can be accomplished in a variety of ways. Linux distributions, including Ubuntu and Fedora, have abase64command line utility. Soecho "data:image/png;base64,`/usr/bin/base64 -w 0 rg_rotate_touch_icon.png`" > \ rg_rotate_touch_icon.b64creates
rg_rotate_touch_icon.b64as a fully specified data URL that will become theapple-touch-iconresource. The “-w 0” argument tobase64disables default line wrapping. Turning off line wrapping is not strictly necessary but is convenient for handling the encoded image as a single element.On Linux as well as other systems, Ruby or Perl can provide a cross-platform capability. The Ruby version would be
ruby -e 'print %{data:image/png;base64,#{[ARGF.read].pack("m").gsub("\n","")}\n}' \ rg_rotate_touch_icon.png > rg_rotate_touch_icon.b64where
pack("m")base 64 encodes its Array target element andgsub("\n","")removes line wrapping (Ruby 1.9+ allows m0 as pack’s argument to remove line wrap). -
Install the base 64 encoded image in the web application file,
rg_rotate.html. Copy and paste the content of the file created in Step 2,rg_rotate_touch_icon.b64, into thehrefattribute of thelinktag that identifies the Apple touch icon to Safari. Thislinktag appears after the openingheadtag of the web application file,rg_rotate.html:<link rel="apple-touch-icon" href="[copy content of rg_rotate_touch_icon.b64 here]" /> -
Encode
rg_rotate.htmlinto base 64:echo "data:text/html;charset=utf-8;base64,`/usr/bin/base64 -w 0 rg_rotate.html`" > rg_rotate_html.b64In Ruby,
ruby -e 'print %{data:text/html;charset=utf-8;base64,#{[ARGF.read].pack("m").gsub("\n","")}\n}' rg_rotate.html > rg_rotate_html.b64which encodes the entire web application, including any resources embedded as data URLs, as a data URL.
-
Install the base 64 encoded web application in the application installer file,
rg_rotate_installer.html. The source forrg_rotate_installer.htmlis shown in Listing 3. Copy and paste the content of the file created in Step 4,rg_rotate_html.b64, into thehrefattribute of theaanchor tag that will be used to install the data URL into the URL Bar on Mobile Safari. For example:<a href="[copy contents of rg_rotate_html.b64 here]">INSTALL RG-ROTATE</a> -
Provision the installation web page. Using a local or remote Web server, e.g, Apache, place the file
rg_rotate_installer.htmlin a location that Mobile Safari on the iPhone can navigate to. A screen similar to Figure 1 will be displayed. Follow the installation steps shown. In the second step of Figure 1, when the “+” key on the iPhone Button Bar is pressed a menu rolls up. Select the menu item “Add to Home Screen”: a dialog screen, “Add to Home”, is activated. The application icon (encoded in Step 2 above) should appear on this dialog screen. Notice that the icon will have been enhanced by Safari with 3D shading and also will have been scaled to fit a 57×57 pixel rectangle. Next fill in the application name and press the Add button. The application can now be launched by simply tapping its icon on the iPhone home screen.

Figure 1: Installation Web Page
As a convenience, we have automated steps 1-5 in a Bash script (Listing 2) which you can download. This script will write an installer HTML file for rg_rotate by running the command: build_installer.sh rg_rotate. Other application installers can be built by providing different application names to the builder script: build_installer.sh [application name] creates the file [application name]_installer.html. Please see the next section, “Extending the Installer”, for more information about enhancing build_installer.sh to handle different applications.
The script prompts for a single image file to be used as the apple-touch-icon resource. The script determines the mime type of the image file from its extension and encodes the corresponding data URL appropriately.
Extending the Installer
Simplified for instructional purposes, the provided generator script prompts for and embeds only one external resource, However, it can easily be enhanced to prompt for multiple images and other resources that particular stand-alone web applications might need. As a first approach, one can add sed commands to replace additional target URL references with base 64 encoded resources. In build_installer.sh, each resource is identified in the source web application with a placeholder such as “RESOURCE_1“. An entry for this placeholder would be added to the script as a sed command of the form:
s~RESOURCE_1~"data:'$mime_type[1]';base64,'`$BASE64 -w 0 $resource_file[1]`'"~;
where the values for $mime_type[1] and $resource_file[1] must be set appropriately. See Listing 2 build_installer.sh for details. A much more robust web application installer could be built with a sophisticated micro-framework such as the Ruby-based Sinatra (see for example, Building a iPhone web app in under 50 lines with Sinatra and iUI), but this approach would require the installation and management of additional resources.
Listing 1: RG Rotate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | <html> <head> <link rel="apple-touch-icon" href="TOUCH_ICON" /> <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;"/> <style> body[orient="portrait"] { background-color: #f00; } body[orient="landscape"] { background-color: #0f0; } body[orient="portrait"] #content { width: 100%; height: 460px; } body[orient="landscape"] #content { width: 100%; height: 300px; } </style> <script> var currentWidth = 0; addEventListener("load", function() { setTimeout(orientationChange, 0); }, false); function orientationChange() { if (window.innerWidth != currentWidth) { currentWidth = window.innerWidth; document.body.setAttribute('orient', (currentWidth == 320) ? "portrait" : "landscape"); setTimeout(scrollTo, 100, 0, 1); } } setInterval(orientationChange, 400); </script> </head> <body> <!-- <img src="RESOURCE_1" alt="test jpg" /> <p>TEST JPG: see build_installer.sh for details</p> --> <div id='content'></div> </body> </html> |
Listing 2: build_installer.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | #!/bin/bash # # build_installer.sh # # This script is part of the tutorial about "How to Install # an iPhone Web Application". The script is run with a # single argument, the application name, which defaults to # rg_rotate for the purposes of the tutorial. # # The script demonstrates how to build a web page which can # be used to provision an iPhone web application. The # source file for the web application is expected to be named # [application_name].html and the provisioning web page is # written out as [application_name]_installer.html. # # This script can be enhanced with prompts for additional # resource files and mime types. Alternatively, additional # resources can be hand coded where indicated. # # The body of the provisioning web page holds the complete, # encoded web application within a link that can be selected # in the Mobile Safari browser. When this link is selected, # the data URL that encodes the web application is placed # in Safari's URL Bar. At this point the application can be # installed with the + key of the Safari Button Bar. In order # for Safari to access the provisioning web page, it must # be placed on a preferably local web server that can be # reached by the iPhone. # # The provisioning web page is generated to be run as a mini # iPhone application itself. For example, it will respond to # orientation changes and hides the URL Bar when loaded. # # BEGIN RESOURCE MAPPINGS # change the following dummy resource mappings as needed: # Specifically, for each N define the mapping in this file # as: mime_type[N]='type/subtype'; resource_file[N]='file_path' # and the label RESOURCE_N as the corresponding URL reference # in the web application file. # E.g., mime_type[1]='image/jpg'; resource_file[1]='myface.jpg' # and <img src="RESOURCE_1" alt="myface" /> in the web # application file. mime_type[1]='image/gif'; resource_file[1]='/dev/null' mime_type[2]='image/gif'; resource_file[2]='/dev/null' mime_type[3]='image/gif'; resource_file[3]='/dev/null' # END RESOURCE MAPPINGS MAX_RESOURCES=${#mime_type[*]} map_resource() { local ret=$1 local id=$2 local resource='s~RESOURCE_${id}~data:'${mime_type[$id]}'\;base64,'\ `$BASE64 -w 0 ${resource_file[$id]}`'~\;' eval "$ret=$resource" } BASE64="/usr/bin/env base64" XARGS="/usr/bin/env xargs" TEMPFILE="/usr/bin/env tempfile" application=${1:-rg_rotate} touch_icon_image_file="${application}_touch_icon.png" if [[ ! -e "${application}.html" ]]; then echo "Template web application file ${application}.html does not exist." echo "Please create and run application installer again -- exiting." exit fi read -p "Enter location of apple-touch-icon image file (./$touch_icon_image_file): " ipath [[ "$ipath" ]] && touch_icon_image_file="$ipath" if [[ ! -e "$touch_icon_image_file" ]]; then echo "$touch_icon_image_file does not exist, will create application with empty bookmark icon" touch_icon_image_file=/dev/null fi echo "Using ${application}.html as input template for web application" touch_icon_mime_type="image/${touch_icon_image_file##*.}" TFILE=`$TEMPFILE` trap "rm -f $TFILE" 0 1 2 5 15 # TODO: hand code or prompt for additional resources additional_resources_map= for i in $(seq 1 $MAX_RESOURCES); do map_resource res $i additional_resources_map="$additional_resources_map$res" done cat <<EOM1 | $XARGS -I{} sed -e {} ${application}.html > $TFILE 's~TOUCH_ICON~data:'$touch_icon_mime_type';base64,'`$BASE64 -w 0 $touch_icon_image_file`'~; \ $additional_resources_map \ ' EOM1 echo "Creating `pwd`/${application}_installer.html" echo "Point Mobile Safari to the HTTP location of this file to install ${application}" # # Provisioning web page: cat <<EOM2 > ${application}_installer.html <html> <head> <meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" /> <style> body[orient="portrait"] { height: 460px; } body[orient="landscape"] { height: 300px; } </style> <script> var currentWidth = 0; addEventListener("load", function() { setTimeout(orientationChange, 0); }, false); function orientationChange() { if (window.innerWidth != currentWidth) { currentWidth = window.innerWidth; document.body.setAttribute('orient', (currentWidth == 320) ? \ "portrait" : "landscape"); setTimeout(scrollTo, 100, 0, 1); } } setInterval(orientationChange, 400); </script> </head> <body style="background-color: #d0d0d0; font-size: 24px; height: 460px;"> <h2 style="text-align: center;"> <span style="background-color: #000; color: #fff;">"${application}"</span> <br />Installation Steps </h2> <ol> <li>Select <a style="background-color: #ff0; font-weight: bold;" href="data:text/html;charset=utf-8;base64,`$BASE64 $TFILE`"> this link </a> to install "${application}" as a data URL in the URL Bar, then</li> <li>Press <span style="background-color: #ff0; font-weight: bold;">+</span> key on iPhone button bar to add "${application}" to home page. </li> </ul> </body> </html> EOM2 |
Listing 3: rg_rotate_installer.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | <html>
<head>
<meta name="viewport" content="width=device-width; initial-scale=1.0;
maximum-scale=1.0; user-scalable=0;" />
<style>
body[orient="portrait"] {
height: 460px;
}
body[orient="landscape"] {
height: 300px;
}
</style>
<script>
var currentWidth = 0;
addEventListener("load", function() {
setTimeout(orientationChange, 0);
}, false);
function orientationChange() {
if (window.innerWidth != currentWidth) {
currentWidth = window.innerWidth;
document.body.setAttribute('orient', (currentWidth == 320) ?
"portrait" : "landscape");
setTimeout(scrollTo, 100, 0, 1);
}
}
setInterval(orientationChange, 400);
</script>
</head>
<body style="background-color: #d0d0d0; font-size: 24px; height: 460px;">
<h2 style="text-align: center;">
<span style="background-color: #000; color: #fff;">"rg_rotate"</span>
<br />Installation Steps
</h2>
<ol>
<li>Select
<a style="background-color: #ff0; font-weight: bold;"
href="[copy contents of rg_rotate_html.b64 here]">this link
</a>
to install "rg_rotate" as a data URL in the URL Bar, then
</li>
<li>Press <span style="background-color: #ff0; font-weight: bold;">+</span>
key on iPhone button bar to add "rg_rotate" to home page.
</li>
</ul>
</body>
</html> |

