Open apps with hyperlinks on iOS

Important update: iOS 9 introduces the possibility for native apps to handle links to normal websites. This feature will make this hack unnecessary.


On some pages of my website I’ve added a button to follow me on twitter. It’s a custom implementation that doesn’t use twitter’s official iframe for performance reasons and falls back to a simple link if JavaScript is disabled or fails to load. On iOS I wanted this button to open the twitter app though.

The problem is that Apple’s URL schemes provide no fallback if an app is not installed. If you add a link to twitter://user?screen_name=max_hoffmann for example and the user hasn’t installed the app, mobile Safari only shows an error message that the URL could not be opened.

This is what my custom popup implementation looked like before adding iOS specific behavior:

function openPopup(event) {
  // don’t open the link’s href
  event.preventDefault();

  // twitter’s popup size
  var width = 550;
  var height = 570;

  // position popup in the center of the screen
  var top = screen.height / 2 - height / 2;
  var left = screen.width / 2 - width / 2;

  // open popup
  window.open(
    "https://twitter.com/max_hoffmann",
    "Follow @max_hoffmann on twitter",
    "width=" + width + ",height=" + height + ",top=" + top + ",left=" + left
  );
}

To workaround iOS’s error message if the URL scheme could not open an app I changed the code to load twitter’s URL scheme in an iframe instead. This opens the app if it’s installed and doesn’t show the error message otherwise.

The last problem to solve was that there is no way to detect if the iframe threw an error. It should still open my profile on twitter’s website if opening the app failed. First I tried using the page visibility API to detect if the page was pushed to background after the user clicked the button, which would indicate that it opened the app. Unfortunately this didn’t work so I had to use another workaround:

  1. get a timestamp before inserting the iframe
  2. add a function add the end of the current event loop via setTimeout
  3. insert the iframe
  4. at some point in time the scheduled function will be called
  5. calculate if less than 30ms passed

Final Code

function openPopup(event) {
  // don’t open the link’s href
  event.preventDefault();

  var username = 'max_hoffmann'; // use your username here

  // twitter’s popup size
  var width = 550;
  var height = 570;

  // position popup in the center of the screen
  var top  = screen.height/2-height/2;
  var left = screen.width/2-width/2;

  // detect iOS
  if (['iPhone', 'iPad'].indexOf(navigator.platform) > -1) {

    // create iframe with twitter:// url scheme
    var iframe = document.createElement('iframe');
    iframe.src = 'twitter://user?screen_name='+username;

    // hide iframe visually
    iframe.width = 0;
    iframe.height = 0;
    iframe.frameBorder = 0;

    // get timestamp before trying to open the app
    var beforeSwitch = Date.now();

    // schedule check if app was opened
    setTimeout(function() {
      // if this is called after less than 30ms
      if (Date.now() - beforeSwitch < 30) {
        // open fallback url in browser
        window.location = 'https://twitter.com/'+username;
      }
    });

    // add iframe to trigger opening the app
    document.body.appendChild(iframe);
    // directly remove it again
    iframe.parentNode.removeChild(iframe);

    return;
  }

  // open popup if user is not on iOS
  window.open('https://twitter.com/'+username, 'Follow @'+username+' on twitter', 'width='+width+',height='+height+',top='+top+',left='+left);
},

This workaround can be used for other apps on iOS as well. Take a look at this list of URL schemes for more schemes of popular apps. Let me know if this code could be improved. I’m thankful for feedback.