Engineering Blog

November 7th, 2013

Whoa! That Embedded Web View Looks Hot in Your iOS App!

This post comes to us from Allen C., an engineer on our mobile team. The mobile team has dozens of innovations under their belt, and today Allen explains how the iOS team uses HTML views to quickly roll out features that originate on the web.


In the third quarter of 2013, the Yelp mobile app was used on more than 11 million unique mobile devices on a monthly average basis. We’re continuously pushing the envelope to make the app user experience as great as possible. A common requirement in our app is displaying embedded web content for a variety of different features. One such feature is our new Yelp Platform, which allows users to order food from participating businesses directly from our site and mobile apps. In this blog post we’re going to walk you through building a seamless embedded web content experience for your native app on iOS.

Why do you need to embed web content? Well, sometimes it makes sense in order to take advantage of the great mobile website you’ve already built. Other times, the content you want may only exist on the web. Here are some techniques that we use at Yelp to display gorgeous web content, while preserving the great experience our users have come to expect from the app.

The typical method for displaying web content in an iOS app is to create a UIWebView and pass it a URL to load. If you only do that, you might end up with something that looks like this:

6a00d83452b44469e2019b00cab78a970c

This works, but it can be a jarring experience for users, depending on what type of web content you are showing. It feels as though they’ve left your app and entered a scaled-down version of Safari. One striking example, highlighted in the screen capture above, is the lack of a dedicated loading graphic – your user will see a blank screen while waiting for the first page to load. There are several things that are not optimal about this experience:

  1. The look and feel of the web view may be entirely different than the rest of the app.
  2. Navigation and page transitions within the web view are most likely different than the rest of the app.
  3. There is no way for the user’s interaction with web content to directly affect native views in the app.

Since problem 1 is dependent to a large part on the specific app look and feel, this post will focus on overcoming problems 2 and 3. To tame UIWebView we need to give it a controller that implements UIWebViewDelegate. UIWebViewDelegate is a protocol that defines a set of methods which give the view controller significantly expanded control over its web view. We’ll be focusing only on one of those methods:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;

UIWebView calls the method from its delegate (delegation is how Apple’s UIKit framework implements the Model View Controller pattern; a view’s delegate is typically its controller) whenever it is about to load a URL request, either as a result of a user’s action, or loaded programmatically from the app. It passes 3 arguments: itself, the URL request about to load, and a navigation type. The return value is where things get interesting: it’s a flag that tells the UIWebView whether or not to actually load the URL request. If you return NO, the web view will simply not load the request and do nothing. However, that doesn’t mean our app will do nothing – we will add our own code in this method which will specify the app’s response to this URL request.

Native Looking Transitions

The first thing we can do is create native looking animations when loading web pages. Typically, when a user clicks a link in a web view, the web view displays a loading screen and then displays the new page in place once it is finished loading, just like on a mobile browser, and this is what will happen if we return YES in the delegate method. Instead of doing that, we will return NO as discussed above, and then we can take the URL request that the web view wanted to load, and open it in another web view! The new web view can animate into the screen in whatever way best matches the look and feel of our app. The pseudo-code to do this looks roughly like this:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
// Load the first request in place, because there is no web view currently showing
if (self.makingFirstRequest) {
self.makingFirstRequest = NO;
return YES;
}
// The web view that is currently showing originated the request
if (webView == self.visibleWebView) {
[self.hiddenWebView loadRequest:request];
[UIView animateWithDuration:duration animations:^{
// Some desired animation here
} completion:^(BOOL finished) {
UIWebView *oldVisibleWebView = self.visibleWebView;
self.visibleWebView = self.hiddenWebView;
self.hiddenWebView = oldVisibleWebView;
}
return NO;
}
return YES;
}

Voila! We’ve just roughly implemented native looking animations between web page transitions. This isn’t complete yet, but the basic idea is here. One thing we learned while implementing this is that not every new URL request should be loaded in a new web view and animated in. For example a site might load an iframe which relies on being part of the original page. In this case, just opening the iframe URL in a new web view would be incorrect.

Web View Events

We can extend the same concept in order to implement dynamic interactions between web content and the native app. In this case, we simply define a new URL scheme: for example, ‘mobile-event’. When web content needs to interact with the native app, it can simply tell the browser to open a URL with this scheme. At Yelp, we do this by having the mobile site load an iframe with this custom URL and immediately close it. The app will detect this URL being opened, and must respond appropriately to the “web view event” in the delegate method. Here is some pseudo code:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
// Detect a web view event
if ([request.URL.scheme isEqualToString:@"mobile-event"]) {
// Execute code here for the event
// Make sure to return NO or the web view will try to load a fake URL
return NO;
}
// Execute normal URL request handling logic
}

What the application does in response to a web view event is context dependent, but one example is to either load a new non-web view, or to pop back to an existing view on the navigation stack. This allows the flow on our web content to integrate seamlessly with the native app.

Putting it All Together – Yelp Platform

Let’s look at an example from Yelp’s new Platform feature, which allows users to order food from participating businesses directly from the Yelp iOS app (this is available on web and Android too, but let’s focus on iOS right now). The Yelp Platform flow is currently implemented through the mobile site and displayed in the iOS app on a web view. From the Yelp business page, the user can tap the Order Pickup or Delivery button, which loads a web view starting the platform flow on the order form.

6a00d83452b44469e2019b00cad74a970b6a00d83452b44469e2019b00cad792970b

From there the web view controller uses native looking transitions to animate the menu onto the screen.

6a00d83452b44469e2019b00cb1e93970d

Once the user reaches the checkout page and completes the purchase, our mobile site sends a web view event, notifying the iOS app that the purchase is complete. The iOS app then pops back to the business view, now with a nice little alert that the order has been placed and an email confirmation has been sent.

6a00d83452b44469e2019b00cad988970b 6a00d83452b44469e2019b00cabae0970c

What’s Next

We’ve currently got several new features in the works for integrating web content into the Yelp mobile apps and making our user experience that much better. Hopefully, this post will also give you a few ideas for how your own iOS apps can integrate dynamic native looking web content.