Developing an iPhone App using HTML, CSS, JavaScript and PhoneGap - From Concept to App Store

1 August 2011, Danny Connell

Speed Distance Time CalculatorHi. I'm a front-end web designer/developer by trade. When I first heard that I could develop iPhone apps using my existing skills (HTML, CSS, JavaScript), I couldn't wait to get stuck in. Having just got my first app, Speed Distance Time Calculator into the App Store, I thought I'd share an overview of my journey, right from coming up with a concept, through to going live in the App Store.

I feel this sort of overview would've been very helpful to me before I started.

I'll discuss the technologies and resources I used to build a web-app, how I converted the web-app into a native app, submission to iTunes and many of the stumbling blocks and realisations I had along the way.

Concept

I cycle a lot. I cycle to work, to the shops, to mate's houses, all over the show. As a bike geek, I like to work out how long it's going to take me to get places, and measure my average speeds etc. So one of the first apps I looked for as an iPhone user was a speed distance time calculator. I was amazed to find that there was only really one such app in the store. I got it, and found that it didn't really fulfil my needs.

Given this, the concept for my first app was a no brainer - a speed distance time calculator which would satisfy the needs of many people and support as many units of speed and distance I could think of. I figured, even if noone else downloaded it, at least I would find it useful!

Getting started

I'd heard that it was possible to create native apps using HTML/CSS/JavaScript and PhoneGap, so I figured, as a web designer/developer I would use my existing skills rather than learning Objective-C and Cocoa. However, I didn't really know where to begin. A colleague pointed me in the direction of this book, which I promptly ordered. Before I was even halfway through, I felt I was ready to start my own app.

Design

The first thing I did was design the app. I figured it would be helpful to have a Photoshop file with common iPhone graphic elements in order to design an app with that 'native' feel. After some Googling, I found this excellent iPhone GUI PSD. This contains many of the native iPhone elements - buttons, menus, virtual keyboard, pop-ups etc. as well as correctly sized graphics of the iPhone itself in portrait and landscape positions.

iOS4 GUI PSD

iOS4 GUI PSD

After coming up with a simple logo and colour scheme, I set about designing the elements. I wanted to keep it simple, so I decided I would have a simple menu at the top (with Speed/Distance/Time options), two text boxes below for the entry criteria, and a darker box below to display the result. After some tweaking I got this looking fairly tidy and balanced, but realized it left me with a gap at the bottom. I figured this gap could be used to display a good old speed distance time triangle. This would be a nice reminder to the user as to how the values are calculated, as well as another navigational aid.

Design

Design

Creating the web app

Broadly speaking, the process of building an iPhone app using web technologies was a two step process - (1) build a 'web app' - basically just a website in iPhone friendly dimensions and (2) use PhoneGap (or another similar framework, there are several and XCode to transform the web app into a native app that can live on an iPhone. So I set about building the app in it's web-based form.

I decided I would use Dropbox to host the web app within a public folder while I developed it. This meant I could not only work on it from any computer, but also bookmark it on my phone and easily view it and share it with others at any time.

I did all of my development testing in Safari, set to the iPhone user agent (Develop > User Agent > Mobile Safari 4.1 - iPhone). This gave me iPhone-esque rendering and also allowed me to easily view what was happening with local storage (which I'll discuss later) within the app.

Safari - iPhone User Agent

Safari - iPhone User Agent

Enter jQTouch

There are several mobile frameworks for building web apps - all have pros and cons, so I would suggest doing your research and consider your needs before choosing one. I opted on jQTouch. Mostly because the book I'd just read used this framework throughout, and so I pretty much understood how to use it.

Basic pages and animations

The next step was to style up the pages and get them using basic iPhone-esque transitions (slide, flip etc).

In jQTouch, once you've included the framework JS and CSS, setting up pages and animations is a breeze. Basically, you have one HTML page for the whole app, and each app page sits within a div with a unique ID. I had 4 pages - Speed, Distance, Time and Info, so the markup basically looked like this:

<body>
    <div id="jqt">
        <div id="speed">
            ...
        </div>
        <div id="distance">
            ...
        </div>
        <div id="time">
            ...
        </div >
        <div id="info">
            ...
        </div>
    </div>
</body>

Animating between pages with jQTouch is also simple. You just add a link with a href of the id of the page you want it to go to, and then a class to represent the type of animation you want. I.e.

<a class="slideleft" href="#distance">Distance</a>
<a class="flip" href="#info">Info</a>

The first link, when tapped, will go to the distance page with a 'slide left' animation. The latter will 'flip' over to the Info page. Easy!

The only other unusual things I added to the markup were special HTML5 and iPhone attributes. Here is the code for my distance entry input on the Speed page:

<input type="number"
       id="speed-distance"
       placeholder="e.g. 37"
       autocapitalize="off"
       autocorrect="off"
       autocomplete="off"
       tabindex="1" />

The placeholder attribute is fairly straightforward and adds placeholder text to your input that disappears when you tap it. The autocapitalize, autocorrect and autocomplete attributes stop the iPhone from autocapitalizing, autocorrecting and autocompleting on the field. Since we're only entering digits, I had no need for any of these. Finally, the type="number" attribute means that when the iPhone user taps the field, they will be presented with the number keyboard, rather than the qwerty keyboard, making input a hell of a lot friendlier.

Styling up

Styling up the pages was truly a breath of fresh air. I didn't have to worry about cross-browser testing, and I could make use of all the joys of CSS3. This meant most of the elements within the app are CSS based and there are few images. I styled the pages in such a way that elements would stretch out when the user tilted their phone to landscape mode. If you want to make use of landscape mode you should bear this in mind before you start using absolute widths etc.

JavaScript

With the app styled up and animating nicely from page to page, I set about the task of adding all the magic. Getting it to work as a web app was fairly straightforward. The basis of my JavaScript file was the jQTouch initialization options followed by all of my jQuery functionality inside the standard document ready event:

var jQT = $.jQTouch({
    icon: 'images/icon.png',
    addGlossToIcon: false,
    startupScreen: 'images/startup.png',
    preloadImages: [
        'images/back_button.png',
        'images/back_button_clicked.png',
        'images/bg.png'
    ]
});

$(document).ready(function() {

    //app functionality

});

The jQTouch options I used are fairly self explanatory. 'icon' dictates the image of the icon that will be added to the user's home screen if they save the web-app. 'addGlossToIcon: false' will stop the iPhone from automatically adding it's default gloss effect to the icon.

There are many other options that can be set. .

Orientation changes

I wanted several things to happen to the layout whenever the user rotated from portrait to landscape and vice versa. I could make most of these changes using CSS, as jQTouch automatically adds a class of either 'landscape' or 'portrait' to the containing div whenever orientation is changed:

<div id="jqt" class="landscape">

However, some changes had to be made using JavaScript (e.g. when changing to landscape, the SDT triangle fades out, and a newly located 'Clear' button fades in). Tapping into orientation changes for the web app was fairly simple:

$(function(){
    $('body').bind('turn', function(event, info){
        if (info.orientation == 'landscape') {
            // landscape changes
        }
        if (info.orientation == 'portrait') {
            // portrait changes
        }
    });
});

This worked a treat for the web app. However, I later found that this fell apart in the native app. I will discuss this and my workaround later on.

Note: As I mentioned earlier, I would advise using percentage widths on any elements that need to stretch for landscape. Avoid, for example, setting your container div to 300px wide for portrait and 420px wide for landscape, as this results in very ugly transitions when you tilt the iPhone. Instead, have a padded outer container and make your container 'width: 100%'.

Landscape mode

Landscape mode

Local storage

Once I'd got my app working, calculating correctly and adapting to orientation changes I realised I needed to save data for several reasons. Firstly, I wanted to save the user's form values whenever they made a change so that they could resume where they left off. I also wanted to store the user's last visited page, and other data. Achieving this was easy thanks to local storage, a HTML5 technology that allows you to store simple data on the user's device.

Local storage is very simple to use. The following code creates a local storage variable called 'myValue' with a value of 30:

localStorage.myValue = 30;

And to retrieve a local storage value:

getMyValue = localStorage.myValue;

Piece of cake, hey? You can store as many values as you like.To view exactly what is happening with local storage in Safari, click Develop > Show Web Inspector and click on Storage at the top of the Web Inspector:

Viewing local storage in Safari

Viewing local storage in Safari

You can also create a local database on the user's phone, and control this with SQL! I had no need for a database for this project, so won't go into it. However, I've had a dabble and can assure you it is pretty straightforward!

Testing the web app and my first major stumbling block

With the app doing everything I wanted it to do, I set about testing it myself and getting mates to test it by simply sending them the Dropbox link and asking them to add it to their home screen. When using it leasurely, I found no issues. However, my mate (a games tester by trade) found a major issue which turned out to be my first major stumbling block.

The issue being, if you tapped a link to a page (e.g. 'Speed') and then very quickly tapped another link to a different page (e.g. 'Info) then jQTouch would try to fire both animations at once. This resulted in either a very ugly transition animation, or an all out crash!

After days of Googling I found very little information, but eventually found someone that divulged the cause of the problem. The problem is that there is a short delay on tap events, between the actual tap happening and the event being fired (300ms I think). This is to allow for 'double tap' events (such as when you double tab a web page in Mobile Safari to zoom in). This means that if you tap several links within that 300ms, all their respecive events will try to fire together.

To work around this, I had to override any click (or tap) events in the app with 'touchstart' events. These fire as soon as you tap an object (no delay). The code for this went a little like:

var animating = false;

$('a[href=#speed], a[href=#distance], a[href=#time]').not('a.on').bind('touchstart', function(e) {
    if(animating == false) {
        animating = true;
        e.stopPropagation();
        var pageToGoTo = $(this).attr('href');
        var animationType = $(this).attr('data-anim');
        jQT.goTo(pageToGoTo, animationType);
    }
    else {
        e.stopPropagation();
    }
});

First of all we declare a variable called 'animating' and set it to false. We will use this to stop things from firing when stuff is animating (thereby stopping those crashes). Then I bind a 'touchstart' event to all of the navigation links within the app. If the app is not animating, we set 'animating' to true and then use the line e.stopPropagation(). This line stops the usual chain of events (such as the tap event that would usually fire after the touchstart event) from firing. The next three lines simply grab the page we want to go to, grab the animation type (which I've set in a custom HTML attribute named 'data-anim' in the mark-up) and use jQTouch's jQT.goTo function to go to the page that we're after.

We also need to set 'animation' back to false whenever an animation is finished. I accomplished this using jQTouch's 'pageAnimationEnd' event:

$('#jqt>div').bind('pageAnimationEnd', function(event, info){
	animating = false;
})

There was a little bit more to it and than this, but this should be enough for you to fix the problem should you have the same issue.

Becoming an iPhone developer

Before I started nativising my app, I became an Apple Developer and joined the iOS Developer Program. If you want to deploy your apps to your own phone for testing, and submit them to iTunes, you will need to do this. It cost me £59 for the year at the time of writing. If you only want to test your app in the iPhone Simulator, then you can simply register as an Apple Developer for free, and consider joining the program later.

Going native with Xcode and PhoneGap

Ok, with the app now working perfectly as a web-based app, it was time to switch on the Mac and bang Xcode and PhoneGap onto it. Once registered as an Apple Developer, you can grab XCode and the iOS SDK for free. I grabbed PhoneGap from here. To setup my project, I followed the simple steps here.

Getting my app into PhoneGap was basically a case of dropping all my html, css and js files into a www folder within the project and then tweaking a few settings within a file called [project name].plist.

I cleaned my project (Build > Clean) and fired it up in the iPhone simulator. I was overjoyed to find that everything looked and worked perfectly with very little extra work. But then I rotated the simulator and bumped into my second major stumbling block - my orientation changes that worked fine in the web app were being completely ignored!

Second stumbling block - orientation changes

Basically jQTouch's built-in orientation events just simply don't work within PhoneGap.

I was stuck on this issue for several days. I spent hours Googling, and found a few posts with vague solutions, but none of these worked for me (although I think I now know why, more on this later). Anyway, on the brink of despair, I finally came up with my own solution, which basically looks like this:

// get original orientation based on body width
deviceWidth = $('#jqt').width();
if (deviceWidth == 320) {
    currentOrientation = "portrait";
}
else {
    currentOrientation = "landscape";
}

// fire a function that checks the orientation every x milliseconds
setInterval(checkOrientation, 500);

// check orientation
function checkOrientation() {
    deviceWidth = $('#jqt').width();
    if (deviceWidth == '320') {
        newOrientation = "portrait";
    }
    else {
        newOrientation = "landscape";
    }
    // if orientation changed since last check, fire either the portrait or landscape function
    if (newOrientation != currentOrientation) {
        if (newOrientation == "portrait") {
            changedToPortrait();
        }
        else if (newOrientation == "landscape") {
            changedToLandscape();
        }
        currentOrientation = newOrientation;
    }
}

// landscape stuff
function changedToLandscape() {
    alert('Changed to landscape!');
}

// portrait stuff
function changedToPortrait() {
    alert('Changed to portrait!');
}

In a nutshell, all this does is periodically check the width of the container div (#jqt). If the width is 320, we're in portrait. Otherwise, we're in landscape mode. If the orientation changes, we simply fire the appropriate function to do our layout changes.

Making use of native iPhone functionality, stumbling block number 3

Once you get your project into PhoneGap, you open up a world of native iPhone functionality. You can tap into the Accelorometer, Geolocation, Camera, Notifications and more. Check out the PhoneGap API reference to see exactly what you can do. For the purposes of this app, I only wanted to make use of native pop-up messages. The app was using several JavaScript alert messages. However, these have the rather nasty looking title of 'index.html' on them. Native dialogs allow you to customise the title, add multiple buttons and more. I replaced all of my alert boxes with code such as the following:

try {
    navigator.notification.alert("Rotate your display to view longer numbers.", closeDialog, "Tip", "OK")
} catch(e) {
    alert('Tip: Rotate your display to view longer numbers');
}

Using the try/catch method meant that the alerts would still work on the app in it's web-based form. "Tip" is the title that will appear on the dialog box, "OK" is the button text, and closeDialog is a function that is called when the dialog is closed.

After replacing all the alerts, I fired up the app in the simulator. Blast! Didn't work at all! I got stuck on this for several hours. In the end I realised any native functionality had to be fired within PhoneGap's 'deviceready' function. I added the following to my index.html:

<script type="text/javascript" charset="utf-8">
function onBodyLoad()
{
	document.addEventListener("deviceready",onDeviceReady,false);
}
</script>

And this to the body tag:

<body onload="onBodyLoad()">

Note: this code and more can be found in PhoneGap's default index.html (wish I hadn't deleted it!).

Finally, I reformatted my JavaScript file from this structure:

var jQT = $.jQTouch({
    icon: 'images/icon.png',
    addGlossToIcon: false,
    startupScreen: 'images/startup.png',
    preloadImages: [
        'images/back_button.png',
        'images/back_button_clicked.png',
        'images/bg.png'
    ]
});

$(document).ready(function() {

    //app functionality

});

To this:

var jQT = $.jQTouch({
    icon: 'images/icon.png',
    addGlossToIcon: false,
    startupScreen: 'images/startup.png',
    preloadImages: [
        'images/back_button.png',
        'images/back_button_clicked.png',
        'images/bg.png'
    ]
});

//function to check whether we're in PhoneGap
function phoneGap() {
    if (typeof(PhoneGap) != "undefined") {
        return true;
    }
    else {
        return false;
    }
}

function doEverything() {
    // app functionality
}

$(document).ready(function() {
    if (phoneGap()) {
        $(document).bind("deviceready", function() {
            doEverything();
        });
    }
    else {
        doEverything();
    }
});

The phoneGap() function checks whether we are in PhoneGap. All my app functionility goes in the doEverything() function. Then, in the document ready function, if we ARE in PhoneGap, we fire doEverything() inside the 'deviceready' event. If we're NOT in PhoneGap, we simply fire doEverything(). This means that the app will still work in its web-app form (without PhoneGap).

Native pop-up with custom heading and button

Native pop-up with custom heading and button

Getting it on an actual iPhone

Voila! Everything now worked like a charm in the simulator. It was time to bang it on my iPhone. To do this, you need to first generate a 'provisioning profile' - a file that uniquely identifies the app, the phone and the developer - and add this to Xcode. To do this you must be a member of the iPhone Developer Program. You then go to iOS Provisioning Portal, click 'Launch Assistant' and follow the steps.

iOS Dev Center - iOS Provisioning Portal

iOS Dev Center - iOS Provisioning Portal

With the provisioning profile added to Xcode, you have to tweak a couple of settings in the .plist file. Then plug your iPhone in, select the iPhone Device as your active SDK, clean the project and click 'Build and Run'. After a while, your app will pop-up on the device!

Submission to App Store

With everything working on an actual iPhone and tested to death, I was ready to submit Speed Distance Time Calculator to iTunes. There were several things I needed to do this:

  • Plain text description of the app (up to 4000 characters)
  • Website URL
  • Support URL
  • Support email address
  • 512px x 512px icon (for use on the App Store and other places)
  • 320px x 480px screenshots of the app (I used 3).
  • A 'distribution provisioning profile' for the app.
  • A zip of the application binary

Getting the 'distribution provisioning profile' was a case of going back to the iOS Provisioning Portal, choosing 'Distribution' and following the steps.

Creating the application binary was a case of selecting 'Release' as the active SDK in Xcode and clicking 'Build'. This results in a .app file which you simply compress (Control+Click > Compress 'project name.app').

With these things ready, I logged into iTunes Connect, and clicked on 'Manage Your Applications' then 'Add New Application'. I then filled in a big-ass form and finally submitted my App. While waiting, I started designing a promo website for the app. I obsessivley checked the status of my app all week, where it remained at "Waiting for review". Finally, after 6 days, 6 hours and 19 minutes, the status changed to 'In Review' to my delight. It was way past my bed time when I got this news, so I hit the sack, the excitement making it a pretty rough night's sleep.

Going live, kick bollock scramble to get promo site built

I awoke that Saturday morning to find an email saying my app was now in the App Store. It was the quickest I'd leapt out of bed in a long while. From submission to App Store took a total of 6 days, 9 hours and 35 minutes. However, I realised that I hadn't even started building the promo site, and so scrapped all my weekend social plans and got to work.

Promotion, download figures

I smashed out the promo website by Sunday afternoon. I then checked my iTunes Connect App to see if I'd had any hits (I had no idea when the daily download stats appeared). I was pretty chuffed to find I'd had 10 downloads on the first day. I've sinced figured out that I get my daily stats around 12pm each day UK time.

iTunes Connect Mobile App

iTunes Connect Mobile App

Soon after, I signed up to App Figures. This site gives you a much prettier and user friendly method of viewing your download stats. It allows you to view your stats in sexy charts by downloads, date and country.

App Figures - Sales

App Figures - Sales

App Figures - by Country

App Figures - by Country

You can also view rankings and view all of your global reviews at-a-glance. Took me a few days to spot this as the link doesn't really stand-out - go to 'Reviews' then click 'All Countries' on the left to view reviews from all countries.

App Figures - Reviews

App Figures - Reviews

Demo video

I'd read in a few places that it was essential to have a demo video for your app. I would put this on YouTube, put it on the promo website and send it out to review websites. The video would basically be a recording of the app being used in the iPhone simulator in Xcode.

I had trouble finding some free Mac software to record the app in action. In the end I found I already had software to do this - Quicktime. The latest version has a built-in screen recorder:

Screen recording with Quicktime on Mac

Screen recording with Quicktime on Mac

I didn't want the Mac's cursor to be visible on the recording, so I used PhoneFinger. This software transforms the cursor into a small semi-transparent circle, much more apt for representing touching. You can also transform your cursor into a human finger, but I felt this looked a bit toilet.

I wrote a sort of demo script, that I could follow while I made the recording, so that I made sure I showed all of the app's key features. Before recording I made my desktop background white, and removed all of the icons and taskbar thingy so I would have a more or less completely white background.

After recording the raw video, I imported it into some video editing software, covered up any Mac elements that were still around the edges (with plain white images) and added titles and music. This page offers some links to royalty free music that you may find useful. I also edited down the footage to just over a minute (from 3 mins raw footage). I think its always best to keep this sort of video as short as possible. After putting the promo video on YouTube, I added a link to the video on my app description on the App Store, as well as some pop-up links to it on my promo website.

Speed Distance Time Calculator Video on YouTube

Speed Distance Time Calculator Video on YouTube

Epilogue

It's now been just over 2 weeks since the app went live. I'm now just submitting the app to review websites, writing blogs and tweeting about my App. I've also discovered another good App Analytics tool that works cross-platform, Mopapp.

Many thanks indeed.

Resources

8 Comments...

  1. Very, very strong work - both on your app (congrats!) and this post which is a great read. Get the tools out!

  2. hi, its a very nice walk through for mobile app development. I'm also working on 2 mobile apps using jQtouch + PhoneGap. This is worthy tutorial/article. I like it

  3. Hi,

    Thanks for a great post! Very helpful

    /J

  4. If only all tutorials on the internet could be like this. Very informative! I appreciate the time it must have taken for you to put this together. Hope sales are going well.

  5. Just have to say, this must be one of the clearest and most helpful tutorials I've ever read. I like your style - reminds me of the Visual Quickstart guides. Nice work!

  6. rudyBigBoss 14 May 2013 at 2:18pm said:

    I'm trying to connect my phongap webapp on the simulator, but the .js won't get to the .php on my server, i tried everything, and it doesn't connect on my simulator, but it connects on the SAFARI smulator, so... is the app, did you had a trouble like this?

Leave a Reply

Your email address will not be published. Required fields are marked *

* Copy This Password *

* Type Or Paste Password Here *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>