Introduction
Last week, the New York Times revealed that Facebook is working on a major update for its iOS application. There’s nothing inherently newsworthy about this fact. Of course Facebook is working on a major update for its iOS app. However, this particular update is pretty newsworthy. Facebook is planning a significant course correction in terms of how they build and maintain their ever-growing suite of mobile apps.
Until now, Facebook’s publicly-stated mobile strategy has been to deliver hybrid apps on the major platforms in order to avoid ‘writing the same code four times’ (more on this later). In theory, this makes perfect sense: who wants to maintain several codebases that do the same thing when you can have just one? In reality, though, the Facebook app never feels ‘native’ on any platform, suffers from terrible performance problems, and is generally reviled by its users.
This article will look into the topic of hybrid apps in more depth. We’ll start by explaining what constitutes a hybrid app. Then, we’ll look at the benefits hybrid apps can offer, and offer more details on the negative aspects. We’ll examine some of the options that exist for making a hybrid app feel more like an native app, and give you specific suggestions for improving performance, and look and feel. Finally, we’ll return to Facebook, where we will examine the history of their iOS app in more detail, in order to understand how they ended up where they’re at today.
Personally, I wouldn’t build a hybrid app for iOS unless I absolutely had to. I don’t think hybrid apps feel right on iOS: they’re slower and clumsier, and never quite have the look and feel that native apps offer. However, under the right circumstances, they can offer advantages that may outweigh the negatives. Keep reading in order to learn all about hybrid apps, what works, what doesn’t, and whether they’re right for your needs.
What’s a Hybrid App?
Normally, an iPhone application is built purely in Objective C with the Cocoa Touch frameworks. You may have a UITabBarController
that hosts a number of view controllers. The view controllers may be subclasses of UITableViewController, or they may be UIViewControllers
that use XIBs
or define their UIs in code. Sometimes, a view controller might host a UIWebView
control in order to display web content or long form text content inside of an app.
Hybrid apps implement some or all of their client code in HTML, CSS and Javascript instead of Objective C. A given application screen may actually contain a UIWebView
rendering server-side markup and interpreting server-side code instead of Objective C.
Some apps, like Instapaper and Pocket, are highly reliant on web views, using them for critical application functionality. Instapaper and Pocket take a pragmatic approach to their use of web views: the primary function of both apps is to display reformatted web content, so using a web view to display HTML only makes sense.
Further down the spectrum, Facebook uses as much HTML as possible in their iOS application, essentially stuffing a version of http://touch.facebook.com into an Objective C shell. They use Objective C for a limited set of application features, such as the login screen and their once-unique slide-out menu interface.
Lastly, there are mobile websites, which can be added to the iPhone’s home screen. These are 100% HTML and run exclusively inside of Mobile Safari.
A Spectrum of Apps
Type | Example |
Purely native | Path 2.0, and other apps implemented entirely in Objective C with Cocoa Touch. |
Some web views | Instapaper and Pocket both use web views for critical parts of application functionality, but implement everything else in Objective C. |
Minimal Objective C | Facebook, for example. As much of the application is implemented in HTML/JS/CSS as possible, and only a handful of components are native. |
Purely HTML | Just like it sounds. |
Hybrid App Benefits
Why would you want to build a hybrid app? There are a number of reasons, like speed of execution and updates, ease of conducting A/B testing, and expanding your pool of available developers by allowing front-end web developers to contribute to your iOS app.
Speed of Execution
Although developing a mobile web experience that flawlessly looks and behaves natively is nigh-on-impossible, it is relatively simple to deliver a reasonably good experience in a short amount of time. It’s definitely easier to build a decent web experience and load it in a UIWebView
than it is to build a JSON API and the accompanying front end in Objective C. For this reason, some developers may prefer to deliver proof-of-concept features in HTML, and then later replace the feature with native code when the feature’s value has been borne out.
Time to market can be a crucial differentiator for companies, and being able to shave weeks or even months off a delivery schedule can mean the difference between life and death for a startup with limited runway.
Speed of Delivering Updates
Another advantage of providing application features via HTML is that you can update your application’s feature set without ever having to deliver an application update to Apple, and without having to wait through Apple’s week-long approval process. Being able to quickly change, fix and extend your app’s capabilities can offer you a significant competitive advantage over competitors who are inherently less nimble by virtue of having to go through the Apple approval process for every minor tweak.
Being able to envision a new feature in the morning, deliver it by end of day, and watch how users engage with it over night in order to polish, tweak or remove it by morning is an incredibly powerful way to hone in on the experiences your app will need to succeed in the market.
A/B Testing
Related to both speed of execution and update delivery is the ability to rapidly conduct A/B or split tests with your users. For example, you may want to test whether your new application feed feature sees higher user engagement when it includes a ‘Like’ button on the feed itself vs. only on feed item details pages. By implementing your feed feature in HTML you can easily split test this change, showing the button to only one group of users in order to see if statistically significant changes in user behavior are occur.
Performing A/B tests in native code isn’t impossible, but it’s far more challenging to set up the tests, and it will take you a significantly greater amount of time to respond to new data revealed through the tests than it would with a hybrid app. In case you’re curious about how to make this work in Objective C, there’s a great article on Little Big Thinkers that demonstrates how they performed A/B testing on the iPhone.
Democratized App Development
If you’ve ever tried hiring an iOS developer (or if you’re an iOS contractor), you know that the demand for capable iOS developers far outstrips the pool of available talent. Hybrid apps can greatly expand the number of capable developers available to you, since you should be able to repurpose any of your front-end developers for feature development in your iOS app, given that they already know Javascript, HTML, CSS and whatever backend language you’re using on your server.
Hybrid App Negatives
Of course, if hybrid apps had no downsides, no one would build native apps. This obviously is not the case, and there are several reasons for why. In short, hybrid apps are slower, clumsier, don’t look native, and, most importantly, don’t feel native.
Lack of Nitro
Mobile Safari’s Javascript engine, Nitro, is pretty darned fast. Unfortunately, due to security concerns, UIWebViews don’t get to take advantage of this speed-boost:
Perhaps the biggest reason for Nitro’s performance improvements over WebKit’s previous JavaScript engine is the use of a JIT — “Just-In-Time” compilation…A JIT requires the ability to mark memory pages in RAM as executable, but, iOS, as a security measure, does not allow pages in memory to be marked as executable. This is a significant and serious security policy. Most modern operating systems do allow pages in memory to be marked as executable — including Mac OS X, Windows, and (I believe) Android. iOS 4.3 makes an exception to this policy, but the exception is specifically limited to Mobile Safari.
Practically speaking, this means that if your hybrid app makes use of Javascript, it will feel slower than the same UI in Mobile Safari, or the same UI implemented in Objective C. Unfortunately, there’s simply no way around this other than reducing or entirely removing your use of Javascript in UIWebViews
in favor of CSS3 animations and transitions wherever possible (which should be able to take advantage of GPU acceleration).
To be clear, you can accomplish some truly amazing effects using nothing but CSS3, as demonstrated recently by Hakim El Hattab on his stroll.js project. But still, in general, the lack of Nitro is a serious impediment to the success of any hybrid iOS application, especially one trying to match the look and feel of native iOS apps.
Challenges of Mimicking Native UI
In addition to the performance issues described above, the challenges of mimicking a native Cocoa Touch user interface is another difficulty presented by implementing a hybrid app. As has been pointed out innumerable times before, iPhone apps have a very distinct look and feel. Table cells, for example, have standard font sizes, padding and spacing requirements, gradient selection highlights, disclosure arrows, and many other features that can be difficult to replicate.
An alternative is to abandon the requirement of duplicating iOS system controls, and instead build a unique UI for your app, just as Facebook has with their News Feed and Timeline features. However, even if you choose to take this path, there are several WebKit features that must be worked around to make your hybrid app not feel like a web page.
Sometimes Things Simply Go Bad
It’s not unreasonable to expect that, sooner or later, your CSS or Javascript files for your hybrid views will fail to load. Perhaps your users are AT&T customers in New York or San Francisco, with their notoriously fickle GSM networks. Perhaps the gods simply aren’t smiling upon you today. Regardless, eventually you’ll end up delivering a UI to users that looks like this:
…and the worst part is that there’s simply nothing you can do about it, outside of mashing all of your JS and CSS into the HTML page itself. This reflects incredibly poorly upon both you and your application, and it’s entirely uncontrollable. At least with an entirely native app, server errors can be handled more gracefully with an alert popup.
(screenshot from @timanrebel, co-creator of the social skiing and snowboarding app, Snowciety.)
How to Attempt to Make Your Hybrid App Feel Native
UIWebView Drop Shadows and Background Colors
By default, UIWebViews
display a drop shadow and background color or pattern (depending on iOS version and hardware) underneath the rendered page. This looks and feels distinctly non-native, so you’ll need to remove both. Luckily, this is quite easy to do: a couple lines of code in a UIWebView
subclass is all you need to make the view ‘flat’. Check out the MIT-licensed project of mine, FlatWebView, to see an example of how to do this.
Link Highlighting
To help users see where exactly they tapped, WebKit highlights a large semi-opaque rectangle around links when they’re tapped.
Although this is handy for the web, it feels distinctly non-native. To get rid of this behavior, you can include this CSS snippet in your web app:
/* http://stackoverflow.com/questions/9157080/wrong-webkit-tap-highlight-color-behavior-when-page-as-web-standalone-app */ html { -webkit-tap-highlight-color: rgba(0,0,0,0); -webkit-user-select: none; }Touch Delays
By default, WebKit adds a delay of about 300 milliseconds when responding to touched links on web pages. This is actually a feature, not a bug, since it can be pretty common to accidentally tap the wrong link on a web page, and having a slight delay to give the user the opportunity to correct their mistake makes for a far better experience than the alternative. However, when you’re building a hybrid app, you should only be using UI components with hit targets large enough to prevent accidental tapping. (You are using large hit targets with a minimum size of 40×40px, right?)
To that end, when you’re building a hybrid app you must disable WebKit’s touch delay feature to ensure that your app’s UI feels as responsive as a Cocoa Touch UI. Matteo Spinelli offers up some handy Javascript code (not jQuery!) to fix this ‘problem’: Remove onclick delay on Webkit for iPhone.
Scrolling
It used to be that one of the biggest challenges of building a hybrid app was duplicating native scrolling behavior inside a UIWebView. Specifically, it was next to impossible to duplicate the scrolling speed, inertia and ‘rubberbanding’ seen in normal UIScrollView
subclasses in a UIWebView
. Luckily, Apple exposed a scrollView
property for UIWebViews
in iOS 5, which makes supporting these features trivial.
All you need to do is set the decelerationRate
property on your web view’s scroll view to UIScrollViewDecelerationRateNormal
:
UIWebView *hybridView = [self somethingThatGetsOurWebView];
webView.scrollView.decelerationRate = UIScrollViewDecelerationRateNormal;
Bridging Objective C and Javascript
There’s little sense in building a hybrid app if you aren’t planning on taking advantage of some of iOS’ platform capabilities. For instance, even though Facebook today is mostly implemented in HTML, it still uses native UI for posting on other users’ timelines, uploading photos, pull-to-refresh, and other features. Clearly, though, native features in your app must be able to communicate with your UIWebView
-hosted views. Creating a seamless experience between native and web views can be challenging, though, given the poor communication channels that exist between these layers.
Objective C to Javascript
To update a web view in response to native code changes, you have three options:
- Reload the UIWebView’s contents.
- Load a new page using an NSURLRequest.
- Invoke Javascript code in your UIWebView using the -stringByEvaluatingJavaScriptFromString: API.
Reloading the contents of the @UIWebView@—or loading an entirely new page—stinks because it’ll leave you with a blank, inoperable page for a not-insignificant amount of time. You can always put up a loading HUD view (e.g. SVProgressHUD), but that’s still not a great experience.
Updating your UIWebView by executing Javascript code within it is a somewhat better option: it gives you the opportunity to perform partial updates, for instance. However, it presents its own challenges, like the incredibly poor debugging experience that you’ll receive when something doesn’t work right.
Javascript to Objective C
To update native code in response to changes in your web views, your best bet on iOS is to implement the UIWebView
delegate method -webView:shouldStartLoadWithRequest:navigationType:
. You can define custom schemes (e.g. myappname://
instead of http://
) with custom URIs (e.g. showImageCapture
instead of apple.com) in order to invoke native platform features from a UIWebView
. Unfortunately, this solution tends to degenerate into enormous if-blocks for even moderately complicated apps.
Facebook, for instance, may implement their delegate method like so:
if ([request.url.scheme isEqual:@"showImagePicker"]) { // show a UIImagePickerController } else if ([request.url.scheme isEqual:@"sendMessage"]) { // show the send message view controller } else if ([request.url.scheme isEqual:@"checkIn"]) { // show the places checkin view controller } else if ([request.url.scheme isEqual:@"postStatus"]) { // show the post status view controller }Fortunately, there are ways to simplify this. For instance, you could use an NSDictionary
to map schemes onto actions in a sort of ad-hoc dispatch system.
- (void)viewDidLoad
{
self.dispatch = [NSMutableDictionary dictionary];
[dispatch setObject:[NSValue valueWithPointer:@selector(showImagePickerForURL:)] forKey:@"showImagePicker"];
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSValue *selectorPointer = [self.dispatch objectForKey:request.url.scheme];
if (selectorPointer)
{
[self performSelector:[selectorPointer pointerValue] withObject:request.url];
return NO;
}
else
{
return YES;
}
}
When the Facebook app for iPhone first premiered, it was an almost entirely native application, written (as far as I know) single-handedly by Joe Hewitt, who extracted the Three20 iOS framework from its meaty core. If you’ve ever written an app with Three20, you’ll know it’s a bit unwieldy. Odds are, sometime after Joe quit iOS development, the powers that be at Facebook decided that the existing implementation was simply too hard to maintain, and it was time to reconsider course.
This internal sea change was described at some length by Dave Fetterman, Engineering Manager on Facebook’s platform, in a talk at last year’s F8 conference:
You have four different platforms to build for something essentially. You want to build for all of those groups? You are going to have to build the sucker four times. Then there are all of the features – groups, deals, the new profile. All of this stuff and the matrix got really bad. So, we have to build things four times which means that the code gets slow. The code gets old. There are different versions of parity and things just don’t work together which makes it extremely difficult for a fast moving company like Facebook.
So, when Facebook 4.0 shipped for iOS, it represented this new ‘Write Once, Run Anywhere’ mentality. I must say that, personally, I think this is a great idea in theory. No one wants to build and maintain the same feature set four times, especially a company like Facebook, which has amazingly high user-to-engineer ratios. Unfortunately, the reality of the situation is that UIWebViews
are simply not robust enough to handle the needs presented in a rich application like Facebook, and its users know it:
Conclusions
In conclusion, hybrid apps don’t feel quite right on iOS: they’re slower and clumsier, they can suffer from unrecoverable errors, and they just don’t ‘feel’ as good as native apps do. But, they offer several advantages that may, under the right circumstances, be a worthwhile tradeoff. Personally, I recommend that you always plan to build your iOS app’s user interface entirely in native code, and then selectively implement portions of it as hybrid functionality as need arises, whether that need is pure speed of execution, the ability to push updates instantaneously, or the ability to test multiple variations of features on users in a short span of time.
At the end of the day, though, only you know what’s really right for your users and for your business. Just make sure that the choices you make are made for the right reasons, and not purely for the sake of expediency.
What do you think about hybrid apps? When do you think the positives outweigh the negatives? Do you have other tips and tricks for making the hybrid app experience better? Need more advice on how to go from hybrid to fully native? Let us know in the comments, we’d love to hear from you about this!