Cloud Four Blog

Technical notes, War stories and anecdotes

Seven Things I Learned While Making an iPad Web App

Earlier this year I was in the Cloud Four office with my iPad, so I decided to show @grigs what we’d done about orientation change for Lucid Meetings. He looked at it, then got kind of frowny faced and said:

“Do you need to use the browser forward and back buttons in Lucid?”

Naturally, this wasn’t the reaction I was expecting, but I said “Nope! Uh, why do you ask?”

And that’s when Jason suggested that, while the orientation support was fine, we were losing a lot of screen real estate to the fat browser chrome, and wouldn’t it be even nicer to have use of that space?

Thus began my foray into making a “traditional web application” perform double duty as an “iPad web application.”

1. Removing Browser Chrome

So, I wanted to remove the browser chrome, but what that meant was that I actually needed to do the following:

  • tell the browser that Lucid Meetings was “web-app-capable” (yes)
  • tell the browser what kind “web-app-status-bar” to use (black)

I added these lines to the HEAD element:

  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="apple-mobile-web-app-status-bar-style" content="black" />

Then I refreshed and… nothing changed. Read, read, read. Oh, I see. I needed to save the application to my home screen by pressing that little icon thing and selecting the Save to Home Screen option (and, I thought, how many users will know to do this???). I relaunched the site from the saved icon and… victory! The browser chrome disappeared. We’re done. Moving on.

Not so fast, bucko.

2. Staying In The Web App

As soon as I clicked any link or button, the web app immediately closed and the site reopened in the browser. That was weird and unexpected. I never did find the exact answer as to why that occurs for internal site links, but it does and there are a couple ways to deal with it.

One way is to convert the site to a single page application with fragment links or AJAX callbacks. That’s exactly how the real-time meeting engine within Lucid works, so that part was golden. The other way is to bind to the click events and prevent the default browser behavior that causes Safari to open (remember, we are in web app, not in Safari, per se). For the multipage, non real-time portion of Lucid, we took the latter approach.

After a bit of hunting around, I found a neat little jquery extension that does what you need: https://github.com/mrmoses/jQuery.stayInWebApp

For various reasons I didn’t want to hook all the links, so I restricted the hook to those links with the “stay” class. Then I added the “stay” class to nearly every link in the application and added the following jquery snippet:

  $(document).ready(function() {
      $(function() {
          $.stayInWebApp('a.stay');
      });
  });

With this jquery extension in place and those “stay” classes added, the Lucid Meetings application now stayed in full web app mode, and that part of the task was finally done.

3. Add to Home Screen

Now the question became “how do I provide a (non-annoying) hint to people to encourage them save Lucid to their home screen?” I normally don’t save web sites to my home screen unless there’s a compelling reason and I never in the past considered that the site might improve its behavior if I did so.

So I went hunting around again and found this little gem: http://cubiq.org/add-to-home-screen

The introduction for this script captures it all:

I found that many iPhone and iPad users don’t know that they can add their favorite web sites to the Home Screen and interact with them like standard native applications. This script helps them to discover this great feature and suggests the steps needed to add your web app to the dashboard.

This script works exactly as advertised and is highly configurable (to reduce the annoyance factor). It also works quite well with the “apple-mobile-web-app-capable” meta tag set to “yes.” That is, when you relaunch as a web app, the script is smart enough not to prompt you, yet again, to save to your home screen.

4. How to Sex it Up a Little

Having gone that far, I now wanted to glam up the web application a bit by adding a startup splash screen and an actual icon for the home screen (to replace the auto-generated screenshot icon). This is a well trod path, though I did have to futz with things a bit. In the interest of putting all the pieces in one place, here are a few more things we added to the HEAD element:

  <link rel="apple-touch-icon" href="http://3w7ov13ob0hk2kk1w147yffjlu5.wpengine.netdna-cdn.com/images/lm57.png" />
  <link rel="apple-touch-icon" href="http://3w7ov13ob0hk2kk1w147yffjlu5.wpengine.netdna-cdn.com/images/lm72.png"  sizes="72x72" />
  <link rel="apple-touch-icon" href="http://3w7ov13ob0hk2kk1w147yffjlu5.wpengine.netdna-cdn.com/images/lm114.png" sizes="114x114" />
  <link rel="apple-touch-startup-image" href="http://3w7ov13ob0hk2kk1w147yffjlu5.wpengine.netdna-cdn.com/images/startup-iphone.png"  sizes="320x460"  media="(max-device-width: 480px) and not (-webkit-min-device-pixel-ratio: 2)" />
  <link rel="apple-touch-startup-image" href="http://3w7ov13ob0hk2kk1w147yffjlu5.wpengine.netdna-cdn.com/images/startup-iphone4.png" sizes="640x920"  media="(max-device-width: 480px) and (-webkit-min-device-pixel-ratio: 2)" />
  <link rel="apple-touch-startup-image" href="http://3w7ov13ob0hk2kk1w147yffjlu5.wpengine.netdna-cdn.com/images/lmsplash1004.png"    sizes="768x1004" media="screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation:portrait)" />
  <link rel="apple-touch-startup-image" href="http://3w7ov13ob0hk2kk1w147yffjlu5.wpengine.netdna-cdn.com/images/lmsplash748.png"     sizes="1024x748" media="screen and (min-device-width: 481px) and (max-device-width: 1024px) and (orientation:landscape)" />

The first three link elements refer to icons in the standard sizes defined by Apple: 57×57, 72×72, and 114×114. The device selects the icon that’s right for it, with the 114×114 icon working for the non-retina iPad. As you might tell, the Lucid web app mode is also setup for iPhones, though we haven’t yet built our dream user interaction model for handsets. More goodness to come!

The next four link elements refer startup images. Three of them are portrait mode images for the iPhone, iPhone with retina display, and iPad. The fourth is a landscape startup image for the iPad. Note that the sizes here, as shown in the “size” attributes, are absolutely required or the startup image will simply be ignored. Yep, found that one. A couple times.

While trying to get the right startup image to show I saw some references to the associated media queries, so we ended up adding them as well. I didn’t dive all the way in to see if both the “sizes” attribute and the media query are required, but I can say that the above examples all work on their respective devices.

5. How to Keep the Display from Scaling on Orientation Change

At this point we had a pretty nifty full screen web app, but unlike the game of Horseshoes, close enough isn’t good enough — so more tweaking and optimization were called for. In this case, I wanted the application to fit well into the display for both portrait and landscape modes, and I wanted the behavior to feel more like an application than a website.

The starting point for all this activity is to set the viewport meta tag to indicate that the application is sized appropriately for the display and doesn’t need to be scaled THANKYOUVERYMUCH. It turns out, however, that we were not sized correctly. Hmm. Fortunately for us only a minor amount of tweaking was required, so we made a few judicious changes and optimized our application so as not to exclude iPad compatibility. I think of this as mindfulness of design and it’s the same thinking we’ve applied when working on the IE7 user experience (yes, that does matter).

With the layout fixed, we added the viewport meta tag to set the initial scaling. For completeness, I’m also showing the CSS inclusion, which shows that we think in terms of landscape, followed by switching to portrait mode. That’s my desktop legacy shining through. Woot. Don’t worry, it works if you go the other way too. One special note: the “(max-device-width:1024px)” part of the media query is present to ensure this only applies to tablets. There are some technical (read: foreign technology integration) reasons why this needs to be the case, and this isn’t the right place to delve into those, but rest assured we’re drumming those out as well.

  <meta name='viewport' content='initial-scale=1.0,width=device-width' />
  <link type="text/css" rel="stylesheet" media="all" href="http://3w7ov13ob0hk2kk1w147yffjlu5.wpengine.netdna-cdn.com/css/landscape.css" />
  <link type="text/css" rel="stylesheet" media="only screen and (max-device-width:1024px) and (orientation:portrait)" href="http://3w7ov13ob0hk2kk1w147yffjlu5.wpengine.netdna-cdn.com/css/portrait.css" />

So, we were all done, right? Not quite. With the iPad, an orientation change results in a scaling operation which means text either zooms in or out, which is quite disorienting for a carefully sculpted application! If you hunt around you’ll find quite a discussion about this, as well as some competing recommendations. From my perspective, an application is different than a website where scaling might be expected. Once again, the internet provides a solution in the form of some custom scripting.

The wonderful Stack Overflow captures this discussion and provides a solution: http://stackoverflow.com/questions/2557801/how-do-i-reset-the-scale-zoom-of-a-web-app-on-an-orientation-change-on-the-iphon

  <script type="text/javascript">
    /*
     * This bit of code disables user scaling on iOS until the user tries to scale with pinch/zoom.
     * http://stackoverflow.com/questions/2557801/how-do-i-reset-the-scale-zoom-of-a-web-app-on-an-orientation-change-on-the-iphon
     */
    if (navigator.userAgent.match(/iPhone/i) || navigator.userAgent.match(/iPad/i)) {
        var viewportmeta = document.querySelector('meta[name="viewport"]');
        if (viewportmeta) {
            viewportmeta.content = 'width=device-width, minimum-scale=1.0, maximum-scale=1.0, initial-scale=1.0';
            document.addEventListener('gesturestart', function () {
                    viewportmeta.content = 'width=device-width, minimum-scale=0.25, maximum-scale=10';
                }, false);
        }
    }
  </script>

As the embedded comment suggests, the script detects that we are on an iPad device and changes the viewport meta tag to disable scaling by setting the minimum and maximum scale to the initial scale. In addition, if the user performs a pinch/zoom gesture (I don’t see this working reliably with double-tap in practice) then we hook the gesture start and “unlock” the scaling capability. In this way, we are pushing our “don’t scale me” agenda, while leaving an option for the user to override our default behavior.

6. How to force a numeric keyboard

Okay, this is a small one, but it DROVE ME CRAZY. One easy thing you can do to say “I love you” to your users is to use HTML5 input types, such as email, url, date, number, and search. In addition to validation possibilities, devices with configurable keyboards may be able to configure themselves with the right keys to speed input. A number field probably doesn’t mean you need to see a traditional QWERTY keyboard.

On the iPad, a “number” input type activates a spinner control that allows you to select from a list. This really doesn’t work, for example, when entering a 16-digit credit card number. For the life of me I couldn’t figure out how to get what I wanted: a simple numeric keypad. It wasn’t until Brad Frost started a discussion on the topic that it clicked for me: add a pattern attribute to a text input field. Voila!

  'pattern' => '[0-9]*

7. Resources Are Available

We’ve only scratched the surface for what we have planned for Lucid Meetings, but what I’ve discovered is we have a vibrant, noisy community of people ready to help. I want to highlight one resource in particular, the Mobile Web Best Practices site, which is a sort of meta resource for me. Grab a beer and check out the resource page. It’ll be a fun afternoon. See you on the other side.

13 Comments on “Seven Things I Learned While Making an iPad Web App”

  1. Justin Avery says:

    Awesome overview John. I’ve previously built similar web based apps but always did the single page ajax thing so it’s good to know about the jquery stayinWebApp.

  2. Sam Striano says:

    Nice article! I do have a question about the first jQuery snippet:

    $(document).ready(function() {
      $(function() {
        $.stayInWebApp('a.stay');
      });
    });
    

    Am I missing something? $(function () {}); is shorthand for $(document).ready(function () {});

    Could you please explain this for me?

    Thank You!

  3. John Keith says:

    @sam

    That is, in fact , what’s in the code, as this snippet is an excerpt from a much larger piece! I think the inner $(function() is cruft that can go, and now I’ll have check it :)

    This cruftiness creeps in over time as we integrate techniques from all over (as you can probably tell). Thanks for the heads up!

    • Sam Striano says:

      @John

      Thanks for the explanation, I just wanted to make sure i wasn’t missing out on some new technique :)

      Sam

  4. Peter says:

    Many users might not know about adding a website to their home screen. But those that do (and probably most occasional visitors) start to get annoyed by frequent popups that teach you this (or tell you about a site’s companion app for that matter.)

    Remember how sites in the 90′s used to offer a helpful Javascript alert prompting you to immediately bookmark the site you just saw for the first time? I think we’re doing that again.

  5. John Keith says:

    @peter I agree about the annoying prompt issue and debated whether or not to go that route. The script I referenced has quite a bit of configurability to help, so it’s worth digging into. Things like only prompting returning visitors, and other ways to reduce the annoyance.

    For the Lucid Meetings case, this web application and prompting behavior is part of the meeting service app itself, which is something users have decided they want to run. I’m banking on that self-interest to make people more amenable to the functionality. If it makes folks unhappy we’ll remove it, but for now our feedback has been positive and of the “oh, I didn’t think to do that” variety.

  6. Great post! Nice round-up of techniques for customizing the experience on iOS. One added benefit of using web-app-capable is that the homescreen icon will appear on the iOS task switcher along with all the other “running” apps.

    Folks should note that web-app-capable web apps launched from the home screen do not share client-side resources (cookies, web storage, web sql db, etc…) with Mobile Safari which can lead to confusing situations for the user.

    Also, there is no way to “link into” the homescreen app. So, if you send an email to a user that includes a link to your app, tapping the link is going to launch your app in Safari, not the homescreen app.

    In conjunction with my first point, this can create a confusing/frustrating experience for the user if the app depends on any client-side data; a session cookie, for example (e.g., “I JUST logged in! Why do I have to log in again?!)

    Of course, these issues might not apply to your app but I thought I’d point ‘em out. Cheers!

    P.S. FWIW – webapps that are capable of running from the homescreen typically make good candidates for PhoneGap-ification which can solve the two issues I raise above. But you’d have to distribute through iTunes which is probably not desirable.

    • John Keith says:

      @Jonathan Thanks for the kind remarks and followup notes. We definitely encounter the “I JUST logged in!” frustration a bit, but don’t have a good answer for that. A savvy user could add the app to their home screen before the authentication step, but we don’t begin suggesting they do so until they’re a bit further into the app usage.

      Creating a PhoneGap version has some appeal, but my thought is we would couple that with other business or technical goals that warranted development and maintenance of another technology and distribution channel. Building a native or hybrid app may enable access to some technology we’d like (such as audio/voip support, for example). Overall, however, we are trying quite hard to stay within “pure” web technologie. Whatever that means! LOL.

  7. Alistair Thomas says:

    Excellent article, just what I needed. Any idea how you can specify a start up URL when adding to the home screen? Doesn’t seem possible. I have a web app that can used in full screen or normal mode, but if it is started in full screen mode I’d like to always show the home screen.

    • John Keith says:

      @Alistair I’m not aware of a way to specify a desired start URL for the add to home screen function. What we do is prompt the user to add (in a hopefully non-annoying manner) on the page we’d like to have them launch into, but overall I don’t think we get to / have to control the user’s experience that much.

  8. Anu Sinha says:

    Hi JK – any idea how to use stayInWebApp in a wordpress environment?

  9. Stephen says:

    Hi John

    Was just wondering if “Staying In The Web App
    ” still works for you after upgrading to iOS 6? I’m having problems with some of my links still opening in Safari.