iOS UIView, WebApps and iFrames


One of our existing responsive websites (built using Angular) was being pulled in as part a Cordova project to make an iOS and Android app, and the developers had some weird issues on iOS. Inside the Cordova app, our app was loaded in an iframe. I wanted to write about a few of the issues that came up, particularly with the iOS UIView, and some of the hackery I resorted to in order to resolve the issues.

Issue 1: Fun with iOS Iframes

First issue we had was that the app kept bouncing between the small and xlarge media query. This turned out to be due to what I consider a bug in iOS, in that iframes size to their content, rather than sizing based on the attributes/styles on the iframe itself or parent constraints. We had logic that enabled some functionality on small, which in turned created a wide html element for a carousel and due to the iframe bug in iOS, would cause the width of the iframe to grow to the xlarge size again. The carousel functionality would be destroyed, as it was only for small, causing the element that was created to hold the slides to be destroyed, causing the iframe to shrink back down to the small size again. And then the cycle would repeat, infinitely.

I was able to fix this issue by immediately setting an explicit width on the body before any of the rest of the HTML was loaded/parsed. This worked in our case because the application will always be in portrait mode, we would need to also watch for resize events and things would get more complicated. Right at the top of the body, I added the following code:

<script>
  document.body.style.width = document.body.clientWidth + 'px';
</script>

Issue 2: iOS WebApp links open in Mobile Safari

The second issue we had was that any link clicked in the website being wrapped in this iframe would load in Safari. It didn’t matter if the link was relative or fully qualified, or what kind of target (or lack thereof) it had. Turns out that is normal behavior for web apps on iOS. I’m not proud of the solution, but the fix was to grab all links in the app, watch the click event, stop the default browser action, and load the new page with window.location. It looked something like this:

$('html').on('click', 'a', function (event) {
  var href = $(this).attr('href');
  if(href && href.indexOf('http') !== -1 && !event.defaultPrevented) {
    event.preventDefault();
    document.location.href = href;
  }
});

I had jQuery available, but for a non-jQuery version, check this out here: https://gist.github.com/kylebarrow/1042026

My initial solution broke links in the site that already had JavaScript functionality attached to them, so the click event was delayed briefly from being attached by wrapping it in a timeout. That way it should be the last event attached, and be skipped if the event had already had preventDefault called on it.

Overall, it’s two hacks that fixed the issues we were seeing. Mileage may definitely vary given, but hopefully this will prove useful to the reader (or more likely, a future me trying to figure this out again).