Some of my favorite feedback on Basecamp for iPhone has been that the app feels wicked fast, and all native. The app actually is a mix of web and native UI, but it’s difficult to see where the line is drawn. The majority of the content shown in the app is web: From the login screen, to posting a message, and even uploading photos on a comment, that’s all done using UIWebView.

Chrome inspired us that web views could be pushed to the next level, and we wanted to make use of our existing fast, mobile HTML5 views. Going web wasn’t always solid: Some views started as web, went native, and ended up back in a web view. In the end, the app evolved into a simple web content browser with a native feel on top. It’s certainly a compromise, but we’ve been really happy with the hybrid architecture so far.

Why web views?

The tech issues with using UIWebView weren’t all solved out of the box. By default the scrolling acceleration is slower than a normal UIScrollView. Caching data with HTTPS didn’t seem to work, so we dropped in SDURLCache. Handling refreshes with content that pops in “above” the current scroll position like a UITableView/UICollectionView is not perfect yet. Despite these issues, there’s plenty of benefits:

  • Rapid iteration on our mobile web views without pushing a new build
  • All mobile web users on any device benefit from new features
  • Document and flow style layout in HTML/CSS is immensely easier than native calculations and sizing
  • Concurrent development of web/native features since the logic is separate
  • No need to worry about CoreData or syncing data up

Native UI is used still for several parts of the app where animations and interactivity are more important than the content displayed.

Where’s the line?

The line between native and web inside of the app is kind of blurry, but there’s a simple rule I’ve used: All content inside of projects are rendered by web views, and everything else is native. A good example of what’s native is the project menu to switch views inside of a project:

For this menu and your list of projects, we used native UI since the layout doesn’t change that much and the interactive feel here had to be as smooth as possible.

Here’s an example of the web views in action: diving into a discussion thread and the author’s page from a comment:

There’s native UI mixed in for the web views as well. There’s a standard UINavigationBar above the content, and we have mimicked the “stacker” effect from the web app using a lot of native animation and gestures.

Browser to app communication

Communication between what’s inside of a UIWebView and in your app isn’t always easy. Most clicks inside of web content are intercepted and then custom behavior gets injected, such as stacking a sheet with new content. Sometimes the web views need to talk back to the app though, and we’ve been using HTML5 data attributes for that. There’s two instances of this so far in the app:

  1. data-remove-sheet=true on a form will pop the sheet off the stack after submission, such as after posting a new message.
  2. data-replace-sheet=true on a link will wipe the existing stack clean, which usually happens if you visit a different project than the one you’re on.

Finding these data attributes are possible thanks to some simple JavaScript and stringByEvaluatingJavaScriptFromString:

def replaceSheet?(path)
  replaceText = web.stringByEvaluatingJavaScriptFromString <<-JS
    var link = document.querySelector("a[href='#{path}']");
    if (link) {
      link.getAttribute("data-replace-sheet");
    } else {
      "";
    }
  JS
  replaceText == "true"
end

This is pretty ugly since we can only get the URL of the link or form making an HTTP request, but it works great.

Hybrid future

The web view approach isn’t right for every app. For Basecamp, we were able to build on the existing infrastructure we had with the mobile views, and make a stunning native wrapper for that content. The pain of dealing with UIWebView’s idiosyncrasies might not be worth it if you’re used to rendering content natively and dealing with the consequences.

We drew the line where we could ship an app that has a great, native feel and still be able to quickly modify how the data in the app is displayed without waiting for Apple to approve it. If you’ve been down this hybrid road before, or just starting, I’d love to hear your experiences either way, good or bad.

Thanks to JZ for feedback, animations courtesy of Ryan, Mig, and Phosphor.