There are many reasons why companies are choosing to use fewer native components in their Apps. I don't want to get into the argument of HTML vs. Native (yet!) but instead share some tips on how I've altered the UIWebView to make it feel more native. If you have any more suggestions, please let me know in the comments below.

There are numerous techniques that can be used to change the look and feel of a UIWebView. I've split them into four categories: Objective-C, CSS, Javascript and Debugging.

Objective-C

Remove the background Shadow

The UIWebView comes pre-packaged with a shadow that is displayed when the user scrolls the view too high or too low. You can remove this shadow by performing the following code:

for (UIView* subView in [self.webView subviews])
    if ([subView isKindOfClass:[UIScrollView class]])
        for (UIView* shadowView in [subView subviews])
            if ([shadowView isKindOfClass:[UIImageView class]])
                [shadowView setHidden:YES];

Disable any data type detectors

The UIWebView automatically creates links out of things like phone numbers and addresses. You may want to prevent this from happening (and handle it manually if you want to perform these types of links). To prevent them from showing, use the following code:

self.webView.dataDetectorTypes = UIDataDetectorTypeNone;

Override -(NSString *)stringByEvaluatingJavascriptFromString:

It may be desirable to control the usage of javascript being injected into the UIWebView. For example, if you're using jQuery, you may wish to force all javascript to wait until the page is ready. You can perform this by wrapping a UIWebView and exposing a new method that wraps each javascript call, like so:

- (NSString *)stringByEvaluatingJavascriptFromString:(NSString *)string
{
    NSString *safeString = [NSString stringWithFormat:@"$(document).ready(function(){ %@ });",string]
    return [self.webview stringByEvaluatingJavascriptFromString:safeString];
}

Note that jQuery permits an unlimited number of $(document).ready() calls.

Using resources in the main bundle

If you are using UIWebView's -(void)loadHTMLString:baseURL: method, store your assets in the bundle (e.g. images, CSS and Javascript files). To access them, set the baseURL to:

NSURL *resourceBaseURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];

Bridging From Objective-C to Javascript

You may apply arbitrary Javascript to a UIWebView by using the-(NSString *)stringByEvaluatingJavascriptFromString: method.

Bridging From Javascript to Objective-C

You may trigger native code by using custom URL protocols. For example, if you wish to display a UIAlertView when a user taps on a link, firstly, set the HREF of the anchor tag to something like “objc://showAlert”. Secondly, override the  -(BOOL)webView: shouldStartLoadWithRequest: navigationType: method to look something like the following:

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    // We give the delegate the change to intervene for everything
    //except UIWebViewNavigationTypeLinkClicked
    BOOL delegateDecision = YES;
    if(self.delegate && [self.delegate respondsToSelector:@selector(myWebView:shouldStartLoadWithRequest:navigationType:)])
    {
        delegateDecision = [self.delegate myWebView:self shouldStartLoadWithRequest:request navigationType:navigationType];
    }
    // we never allow the UIWebView to react to links. Instead,
    // we intercept all those links that start with [self URLPrefix],
    // and discard any others.

    if(navigationType == UIWebViewNavigationTypeLinkClicked)
    {
        // make sure the URL starts with [self URLPrefix]
        if([[[request URL] absoluteString] rangeOfString:[self URLPrefix]].location == 0)
        {
            NSString *message = [[request URL] host];
            [[[UIAlertView alloc] initWithTitle:nil message:message delegate:nil cancelButtonTitle:@"Dismiss" otherButtonTitles: nil] show];
        }
        return NO;
    }
    return delegateDecision;
}

- (NSString *)URLPrefix
{
    return @"objc://";
}

CSS

html{
    -webkit-text-size-adjust: none; /* Do not adjust the size of text (on rotation) */
}

body{
    -webkit-touch-callout:none; /* Remove action menu (cut, copy & paste) */
    -webkit-tap-highlight-color:rgba(0,0,0,0); /* Remove background colour on tap */
    -khtml-user-select:none; /* Prevent user selecting text */
}

Retina support

Suppose that you have a button that receives a background image via its CSS selector, button:

.button
{
    background-image:url('buttonImage.png');
    background-size:44px 44px;
    height:44px;
    width:44px;
}

On retina devices, this image should be replaced by its retina equivalent. To do this, we can provide the following.

@media only screen and (-webkit-min-device-pixel-ratio: 2)
{
    .button
    {
        background-image:url('buttonImage@2x.png');
    }
}

Note: Due to the “cascading” property of CSS, if you put this at the end of your CSS file, your retina images will be used instead of the non-retina images. If you put this before your class, it will be ignored (overridden by the non-retina image).

Note: Because the retina image is twice the size of the original, you will need to specify the `background-size` attribute. If you don't, then the images will appear too large.

Javascript

Although not necessary, it will be assumed that you are using the jQuery library.

Touch states

Suppose that we have some elements in a UIWebView that we want to be visibly responsive to touch. As we disabled the highlight colour in the CSS section above, tapping on links no longer visibly respond. Therefore, we must handle this manually.

Unfortunately, CSS3 doesn't have any equivalent for “touch” like it does for “hover” or “active”. Therefore, we shall use Javascript to monitor the “touchstart”, “touchmove” and “touchend” events.

Suppose that we have the following two CSS selectors:

.button
{
    background-color:#FF0000;
    color:#FFFFFF;
}

.personName
{
    color:#000000;
    text-decoration:none;
}

We can add selectors that describe the changes that should be shown on press:

.button .pressed
{
    background-color:#000000;
}

.personName .pressed
{
    text-decoration:underline;
}

Finally, we need some javascript that contains an array (elementsWithPressStates) of selectors that will respond to touch. You can modify this to fit your needs.

function setupHitStates(element)
{
    $(element).on("touchstart",function(event){ $(this).addClass("pressed"); });
    $(element).on("touchend",function(event){ $(this).removeClass("pressed"); });
    $(element).on("touchmove",function(event){ $(this).removeClass("pressed"); });
}

$(document).ready(function(){
    var elementsWithPressStates = [".button", ".personName"];
    for(var i = 0; i < elementsWithPressStates.length; i++)
    {
        var element = elementsWithPressStates[i];
        setupHitStates(element);
    }
});

Debugging

If you are able to test with iOS 6 +, and have Safari 6 + installed, then you can remotely debug your webpage from your desktop machine. To do this, you will need to enable the “Develop” menu in Safari. To do this, open Safari's preferences and select “Show Develop Item in Menu Bar”. When you run your app (not necessarily in the debugger) you can now navigate to this new menu item, select your device and then the UIWebView that you would like to debug.