Our DNA is written in Swift

Radar: Allow Overriding of User-Agent on UIWebView

Here’s another thing that I had discussed with an Apple engineer at WWDC. UIWebView at present does let you easily modify the user-agent header field that it sends to the server. We found this functionality sorely lacking when we needed to change the user-agent in iCatalog. There are some scenarios where the server-side browser-detection fails and you want to override the user agent for example with one to pretend the web view is desktop Safari.

This feature request was filed as rdar://11767306  and on OpenRadar.

There is of course something of a workaround that we found to be working, but it relies on several techniques that might cease to work in a future iOS version.

The first step is to modify the user agent on the very first NSURLRequest that you tell the UIWebView to load.

NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:_product.productURL
// override user agent set?
if (_userAgent)
        [request setValue:_userAgent forHTTPHeaderField:@"User-Agent"];
[_webView loadRequest:request];

What would be fine and good but this change only holds until the first redirect or sub-request for an image or other resource. Some (but not all) of the sub-requests the web view’s delegate is asked whether they should be allowed. I found that if the original NSURLRequest is mutable then the subsequent ones are also mutable and you can imprint the custom agent there as well.

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSMutableURLRequest *)theRequest 
    if ([theRequest isKindOfClass:[NSMutableURLRequest class]])
        // override user agent set?
        if (_userAgent)
            [theRequest setValue:_userAgent forHTTPHeaderField:@"User-Agent"];
    return YES;

But this only works for some sub-requests. I found in my testing that many subsequent requests don’t show here, especially if they are made from JavaScript (i.e. AJAX). That means if you are dealing with a very stubborn web platform that constantly reevaluates your user-agent then you are out of luck faking it there.

Part 2 of this workaround only works of course if the request stay mutable. The iOS docs claim that they are immutable and I believe they only become mutable if you send the original request as mutable. So this is using undocumented behavior and as such inherently unreliable.

Another – rather obscure – workaround has been documented by James Border. You can set a global user default with the “UserAgent” key.

NSDictionary *dictionary = [[NSDictionary alloc] initWithObjectsAndKeys:
   @"Your desired user agent", @"UserAgent", nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dictionary];

I was quite astonished when my attention was pointed to this. This is not documented anywhere.

So I figured it would make UIWebView much more useful if there was a native method to achieve this. Which brings me to my Radar …

Allow overriding of user-agent on UIWebView


For some use cases it is necessary to modify the user agent that UIWebView sends towards the server to get a different version of the content. Such scenarios where we needed that included opening a special version of an online shopping bag or opening the full-version of Facebook instead of the mobile one.

Suggested Solution

Expose a userAgent property on UIWebView that uses the default user-agent when nil or otherwise sends the set string. The reason why this should be a feature in the SDK is that most of the time when dealing with online hosted platform providers you have next to no chance of getting them to fix some detection mechanism that causes their platform to send the incorrect content.

To make it easier for the developer there could be some methods that would give me certain predefined user agents, like Safari for Mac, to be used with the suggested property.

Categories: Bug Reports


  1. Excellent article, I am still stuck in this issue and now once the user agent is set, there is no way to reset it for any URL requests that uses http header