Changing the iOS WebView User Agent to Handle iOS7 Design Changes

iOS7 is here and its design changes are significant. From my perspective though the biggest change for hybrid apps is that the status bar’s space is now allocated to your app. I spent some time thinking about how to deal with this and after a couple iterations I think I’ve found a good solution.

20 More Pixels

One of the nice design changes in iOS7 is that apps can now be truly full screen. They utilize the 20 pixels of status bar at the top of the screen as the app’s content shows through the status bar. Previously the status bar always had a black background. At Mindflash, we’ve found this particularly nice when a user is immersed in a course as the course content now takes 100% of the screen.

This can have some big implications though. At Mindflash we originally designed our app to fit the 1024×748 resolution of the iPad’s screen without the status bar. This isn’t the best thing to do but since every single device on all versions of iOS had this resolution it simplified things in what was a large project. We’ve since fixed this. When we compiled our app in Xcode 5 we found our app to be a tad short. The first obvious solution to fix this was to make the app’s frame 20 pixels smaller when on iOS7. More details on this can be found on Stack Overflow.

This works reasonably well if you have only a single view controller. If you have more than that it gets more complicated. I never got a fully working solution as I deemed it ineffective and looked for something else. The next obvious solution was to handle it by loading some new CSS specific to if you’re running iOS7. It’s easy to do this if all you need to know is whether the user’s running iOS7. You can have your javascript check the browser’s user agent. Unfortunately, we also need to know what version of our native app you’re running. This made our obvious solution more complicated.

iOS7 lets you run apps not compiled in Xcode 5. When it does this, it runs in legacy mode and shows the black status bar making your app’s frame 1024×748 instead of 1024×768. This is great for users because if an app doesn’t have a new release for the new OS ready or the user just doesn’t upgrade their apps, everything should continue to work. Additionally, Mindflash’s iPad app automatically updates the web portion of the app every time a user opens the app and a new version is available.

Thus, it’s likely that we have many users out there on iOS7 running an old version of our app not compiled in Xcode 5 but running our latest web code. In order to determine whether to display the web app in 1024×768 or 1024×748 we need to know both whether they’re on iOS7 and whether they’re on a new enough version of the Mindflash native app.

Appending to the User Agent

It’s easy to check what version of the native app the user is on but how do we communicate that to the web app before it even bootstraps? The answer for us was appending information about the native app version to the existing user agent. I found we could override the user agent in NSUserDefaults easily but we wanted to maintain the existing user agent string and just append our information to this. We found we could do this by creating a temporary webview to pull the user agent before then overriding it with one that has our custom information appended to it.

Now that we have the native app’s version number in the user agent, we can check it and add a class to apply CSS that only affects the app if we’re running iOS7 and on a version of the native app that supports 1024×768 resolution.

The User Agent Hack Is Cool But Why is This Even Necessary?

While having the app show through the status bar looks awesome there are a lot of apps, Mindflash’s included, where some functionality is at the top of the screen. You can’t tap on a button that’s underneath the status bar. Thus, we need to know if the app is showing through the status bar. If it is, we need to make the area where the buttons are 20 pixels taller so there’s nothing tap-able underneath the status bar.

One could argue you could do this by just measuring the height of the browser window. If it’s 768 pixels then we know the app is showing through the status bar. This doesn’t work though if you’re using the same web app for the iPad and the web. If the user’s on the web and has 768 pixels of height you clearly don’t want to do this.

You could add a check of the user agent to see if they’re on iOS in addition to having 768 pixels of available height but that complicates things if you want to allow users to use your web app in Safari without having to get your native app from the App Store. In the end it seemed easier and more reliable to use the version number of both iOS and the native app to determine whether or not the app was showing through the status bar.

Besides, having this information in your user agent makes it easier to check for other things dependent on the native app version. For instance if you add a feature that requires changes to both the native and web apps you can verify whether to activate them on the web app by ensuring the native version is recent enough. In an app like Mindflash’s where the overwhelming majority of changes are done to the web app this has huge advantages because we can ensure that almost all of our new features go out to all users regardless of their native app version.