It is a well known fact among iOS developers that creating screens with complicated formatted text is a hassle. UILabel, UITextView, and friends are not really designed for that purpose. The alternative that is often used to overcome those limitations is the versatile UIWebView combined with some local HTML content and CSS styling.

But the UIWebView itself comes with strings attached. In this post we are going to share three small tips that might help in making the UIWebView look more integrated with the rest of your UI.

Using flashScrollIndicators with UIWebView

Sometimes it makes sense to let the user explicitly know that there is more content than the part that is already visible on the screen. In UIScrollView there's a method called flashScrollIndicators for exactly that purpose; it displays the scroll indicators momentarily. Unfortunately UIWebView - internally relying on UIScrollView of course - does not expose that method. But the trick to use it anyway is simple:

  1. for (id subView in [webView subviews]) {
  2.   if ([subView respondsToSelector:@selector(flashScrollIndicators)]) {
  3.     [subView flashScrollIndicators];
  4.   }
  5. }
for (id subView in [webView subviews]) {
  if ([subView respondsToSelector:@selector(flashScrollIndicators)]) {
    [subView flashScrollIndicators];
  }
}

Just run this loop after your UIWebView gets displayed and the scroll indicator will flash as expected.

Removing the UIWebView background shadows

Let's say you have a UIWebView with a white background and some black text. Users of your app will notice that you are displaying your content with the UIWebView because of the background shadows that iOS automatically adds. If a UIWebView is dragged down it looks like this:

So what we need to do is remove the shadows and set the background color to white:

  1. if ([[webView subviews] count] > 0) {
  2.   // hide the shadows
  3.   for (UIView* shadowView in [[[webView subviews] objectAtIndex:0] subviews]) {
  4.     [shadowView setHidden:YES];
  5.   }
  6.   // show the content
  7.   [[[[[webView subviews] objectAtIndex:0] subviews] lastObject] setHidden:NO];
  8. }
  9. webView.backgroundColor = [UIColor whiteColor];
if ([[webView subviews] count] > 0) {
  // hide the shadows
  for (UIView* shadowView in [[[webView subviews] objectAtIndex:0] subviews]) {
    [shadowView setHidden:YES];
  }
  // show the content
  [[[[[webView subviews] objectAtIndex:0] subviews] lastObject] setHidden:NO];
}
webView.backgroundColor = [UIColor whiteColor];

This will then make the UIWebView in the dragged down state look like this:

That's much nicer and it's not identifiable as a UIWebView any more.

Preloading the UIWebView content before displaying

Something that always gave us a headache is the fact that the UIWebView loads its content after being drawn on the screen. That is the case even if local content is used only. So what the users are seeing on the screen is a blank UIWebView first and only a few milliseconds (or even seconds on slower devices) later the actual content pops in. That just doesn't look good.

Our solution for this problem is adding a preLoadView method to the view that contains the UIWebView. Let's say we are inside the didSelectRowAtIndexPath of a UITableView that is about to push the next view containing our UIWebView. Instead of calling pushViewController we are doing this:

  1. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  2.   // check indexPath...
  3.   myWebView = [[MyWebViewController alloc] init];
  4.   myWebView.delegate = self;
  5.   [myWebView preLoadView];
  6. }
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  // check indexPath...
  myWebView = [[MyWebViewController alloc] init];
  myWebView.delegate = self;
  [myWebView preLoadView];
}

This will fully prepare the UIWebView and only after the preparation is completely done we're pushing it:

  1. - (void)webViewDidFinishLoad:(UIWebView *)webView {
  2.     [self.navigationController pushViewController:myWebView animated:YES];
  3.     [myWebView release];    
  4. }
- (void)webViewDidFinishLoad:(UIWebView *)webView {
    [self.navigationController pushViewController:myWebView animated:YES];
    [myWebView release];    
}

Of course there are some things to take care of when using this approach. Obviously you need to implement the UIWebViewDelegate protocol. Plus you either have to make sure that the UI does not interrupt when it gets some input while preparing the UIWebView or you have to handle that situation. But we just wanted to give you an idea about how to solve it, you should be able to figure out the details yourself...