Changing the headers for UIWebKit HTTP requests

As of iOS 5, the iOS does no longer use the public API internally to set the UserAgent headers for all of the NSURLRequest objects that are created within UIWebView. So the method that is described here to change the UserAgent headers does no longer work. It relies on the public API for setting HTTP header fields. Therefore the only way left to change the UserAgent headers which works for iOS 5 as well is to write your own HTTP protocol header using the NSURLProtocol class. This is a little bit more complicated than the “method swizzling” approach.

I was asked several times, in which way the “User-Agent” header can be modified for the HTTP requests that are initiated from within the UIWebView object. iCabMobile is doing this, and also some other iPhone Apps, but the UIWebView API doesn’t provide anything which allows to modify the “User-Agent” information or any other HTTP header.

When you load a web page from the internet through UIWebView, you can provide a delegate which is called for each web page that is loaded. And in the method “webView:shouldStartLoadWithRequest:navigationType:” of the delegate, you’ll even get an NSURLRequest object you can look at, but unfortunately you can not modify this object. So there’s no way to change the default “User-Agent” information that is sent to the server, nor can you modify any other data.

When you’re loading data from the internet outside of UIWebView, you would probably use the NSURLConnection class. In this case you would create an NSURLRequest object (or the mutable counterpart NSMutableURLRequest) with all the HTTP headers for the request yourself (using the method “setValue:forHTTPHeaderField:”). You have full control over all of the HTTP headers you want to send to the server, including the “User-Agent” information.

When we assume that the UIWebView object will internally also use NSURLRequest or NSMutableURLRequest to create a HTTP request before this request is passed to the networking classes like NSURLConnection, we need a way to subclass or overwrite the method “setValue:forHTTPHeaderField:” of the NSMutableURLRequest class. Then we would be able to check for each HTTP header that is set for a NSMutableURLRequest, if this is the “User-Agent” header and if it is, we can modify it.

The only problem is that we can’t overwrite or subclass the NSMutableURLRequest class and force UIWebView to use our subclass instead of the original class. But iPhone Apps are written in Objective C and this programming language does allow exchanging and modifying classes, methods, variables etc. at runtime any time. So we can tell the Objective C runtime system that each time the method “setValue:forHTTPHeaderField:” of the “NSMutableURLRequest” class is called, our own method is called instead. This way it doesn’t matter that UIWebView will never call our method directly. Exchanging methods is called “Method Swizzling” and you can learn more about it on the CocoaDev page.

The method swizzling is very powerful, but it can be also very dangerous if you don’t know what you’re doing. So be very careful.

Now to the sources. I’ve implemented the method swizzling as a category of NSObject, so you can use it for all classes very easy (but as I said above, be careful, don’t use it if there are other options).


@interface NSObject (Swizzle)

+ (BOOL)swizzleMethod:(SEL)origSelector withMethod:(SEL)newSelector;



#import "MethodSwizzling.h"

@implementation NSObject (Swizzle)

+ (BOOL)swizzleMethod:(SEL)origSelector withMethod:(SEL)newSelector
    Method origMethod = class_getInstanceMethod(self, origSelector);
    Method newMethod = class_getInstanceMethod(self, newSelector);

    if (origMethod && newMethod) {
        if (class_addMethod(self, origSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
            class_replaceMethod(self, newSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
        } else {
            method_exchangeImplementations(origMethod, newMethod);
        return YES;
    return NO;


You can call “swizzleMethod:” for an object, passing in the selectors of the original and the new replacement methods. If the “swizzleMethod:” method returns with the result YES, each call of the original method will then call the replacement method and each call of the replacement method will call the original method. So within your replacement method you can still call the original method.

Here’s the implementation of the new replacement method for the NSMutableURLRequest class:


@interface NSMutableURLRequest (MyMutableURLRequest)

+ (void)setupUserAgentOverwrite;



#import "MyMutableURLRequest.h"
#import "MethodSwizzling.h"

@implementation NSMutableURLRequest (MyMutableURLRequest)

- (void)newSetValue:(NSString *)value forHTTPHeaderField:(NSString *)field;
    if ([field isEqualToString:@"User-Agent"]) {
        value = @"The new User-Agent string";
    [self newSetValue:value forHTTPHeaderField:field];

+ (void)setupUserAgentOverwrite
    [self swizzleMethod:@selector(setValue:forHTTPHeaderField:)


This new method is implemented as a category, we don’t need to subclass. The replacement method for “setValue:forHTTPHeaderField:” is called “newSetValue:forHTTPHeaderField:” and it is simply checking if the “field” variable is equal to “User-Agent”. If it is, the value is modified. Afterwards the original method is called.
Please note: because the method swizzling exchanges the original and replacement methods, we have to call “newSetValue:forHTTPHeaderField:” to call the original method “setValue:forHTTPHeaderField:”. This looks confusing, but this is the way you can give control back to the original method.

The method “setupUserAgentOverwrite” has to be called once after the App is launched (for example in the Application delegate in the “applicationDidFinishLaunching:” method, or even in “main()”).

   [NSMutableURLRequest setupUserAgentOverwrite];

This should be done before any UIWebView objects are created to make sure that the “User-Agent” is modified for all requests.

You can also use this approach when you need to modify other HTTP headers.

67 thoughts on “Changing the headers for UIWebKit HTTP requests

  1. My own tests under iPhone OS 3.0 have shown that the “webView:shouldStartLoadWithRequest:navigationType:” delegation function does in fact get a mutable NSURLRequest object, and I can add headers to it just fine. Do you know of cases where this won’t work?

  2. @Thomas
    I haven’t tried to modify the “request” parameter of this delegate method, because it is not declared as mutable. So you’re at least not supposed to change the request object here.

    This might work, but you can’t be sure if the request object is not modified within UIWebView after the delegate method returns. Also the delegate method is only called for the main document, but never for images, javascript files, stylesheets and other files that are loaded for the web page. And sometimes it can be important that all requests for a web page do have the same “User-Agent” header.

  3. Thanks Alexander, this works like a charm. also learnt about Method Swizzling.
    Another related question – Could we enable/disable method swizzling based on our needs in the same program – is that possible? For eg, for some use cases i want to change user agent and for some i dont want to..

  4. @Chandu
    Inside your replacement method, you can always include “if” statements so you can modify the headers based on certain conditions. But of course you can also use the “swizzleMethod” method again to exchange the methods multiple times.

  5. This is a very nice and clean solution. Thanks for sharing!
    Do you implement the progress indicator with a similar technique that respects the private API limitations? I only found solutions that where using undocumented API until now.

  6. @Enzo
    This doesn’t work for the progress bar because you would need access to the object/class for which you want to exchange a method. And the progress information is coming from internal private classes. The only way to implement a progress bar without using a private API (at least the only way I know of) would be to monitor/count the web page objects (images, frames, the HTML page itself, the length of the page etc.) that are loaded and that still needs to be loaded using JavaScript. Then you can roughly calculate the percentage of the page that is loaded.

  7. @Alexander
    Thanks a lot for this information. That’s an interesting approach I didn’t yet think of. I was thinking about getting the main document with NSURLConnection and then pass it to the UIWebView. But I think UIWebView is doing progressive load of document and assets, so it would loose quite a lot of speed while page loading with my approach. Sad that Apple doesn’t offer a notification for the progress, this would make waiting for web page loading a more comfortable experience for app users.

  8. – (void)newSetValue:(NSString *)value forHTTPHeaderField:(NSString *)field;
    if ([field isEqualToString:@”User-Agent”]) {
    value = @”The new User-Agent string”;
    [self newSetValue:value forHTTPHeaderField:field];

    are you sure this isn’t going to loop?

    looks like it will call itself indefinitely.

    should this line => [self newSetValue:value forHTTPHeaderField:field];
    be changed to this line: [self setValue:value forHTTPHeaderField:field];

  9. No, this doesn’t loop. The reason is the method swizzling, where the methods are exchanged. So calling method A executes the code of B and calling method B executes the code of A.

  10. It is a terrible idea to promote method swizzling in a public blog post, not to mention it is messing with a system class.

  11. @Concerned Apple Developer
    In my blog post there are explicit warnings about using method swizzling. But you’re right, I should emphasize the warning a little bit…

    Because this feature is part of the Objective-C language, and it’s the only way to workaround certain limitations of the iPhone SDK, I’ve decided to show this as well.

    And honestly, “messing with system classes” is probably the only reason you would need method swizzling at all. Your own classes are under your own control, so method swizzling is not needed here to accomplish a certain task. But system routines are not under your control, so here method swizzling can be the only solution for certain tasks. It’s definitely much better than using a private API, in my opinion.

  12. @Enzo
    Thanks for the link to the “three20” list. I’m not yet sure if method swizzling is a general issue with the new OS 4 or only when modifying certain methods/classes (like such basic stuff as “dealloc”). Until now it seems that Apple has not yet banned method swizzling officially. And if they want to make this a “private” API, they should definitely announce this somewhere…

  13. @Alexander,
    Are you aware of how to handle sites like that use navigator.useragent to detect phone type.. there might be some way to change navigator.useragent value returned by the uiwebview (might be using swizzling).

  14. @Chandu
    Method swizzling can not be used to exchange/modify JavaScript functions and properties. This is a Objective-C thing…

    There’s no easy way to change the JavaScript property “navigator.userAgent”. If you download all the files yourself before passing the files to UIWebView (or if you can hook yourself into the “connection:didReceiveData:” delegate method), you would be able to modify all of the sources. In this case you could simply replace all occurrences of “navigator.useragent” within the sources with a string that contains the new User Agent. But this might not work for all kinds of apps because you have to be able to get the page data before it is passed to UIWebView.

    “navigator.userAgent” is a read only property, so you can’t overwrite it. But because browser sniffing stuff is often done within JavaScript frameworks, it is possible to overwrite the browser detection functions of these frameworks (at least for the more important well know frameworks), but of course you have to know these frameworks well enough to do something like this.
    And this won’t solve all problems. And it gets very complicated…

  15. Alexander,

    FWIW, I had an app approved recently that uses method swizzling in the way you described (for user agent swapping) and it was accepted. However it was an iPad application, so I do not know if they are waiving it because OS 4.0 is quite a ways off for the iPad. Just thought you might want to know.

  16. @JC
    Thanks for the info. The API for method swizzling is still a public API, so I can imagine that Apple has rejected the apps because they “swizzled” some basic methods like “dealloc” and almost everything is affected if something breaks here. So hopefully they don’t intend to remove the method swizzling API.

  17. Hi Alexander,
    I noticed on iCab that you achieve smooth scrolling when the UIWebView has a lot of content. How do you do that? Normally the UIWebView scrolling is jerky when there is a lot of content. Mobile Safari also has smooth scrolling and shows a checkerboard pattern while moving.
    Great work and great blog, by the way.

  18. Hi Alexander,
    great blog and a great app you have over here.
    However my question is the same as Sam.

    I am loading a load of data from the client’s side on a uiwebview, furthermore the javascript keeps on changing the data adding more to the uiwebview’s jerky scroll.
    If you can tell something about the way to avoid it or give an example of the same then me as well as my client will praise you forever plus i will get to eard my bread and butter too xD

  19. Sorry, but I can’t help you with that. There’s no public API to change the scrolling behavior.
    And I don’t want to write about private APIs in this blog, so the “checkered pattern” scrolling of Mobile Safari won’t be covered in this blog, sorry.

    In general when a web page is loading tons of data and/or modifies itself all the time, it is normal that the App is unresponsive for short periods of time. The older and slower the device the better you can notice these short “freezes”.
    In case you “know” the page that is loaded, you might be able to filter out stuff that is not needed.
    In case you inject your own JavaScript code to be able to open links in new Tabs or something like that, make sure to do the initialization of your code as fast as possible. For example, when getting all links of a web page, “document.links” seems to be much slower than “document.getElementsByTagName(‘a’)”, according to some tests I’ve done. So if there are multiple ways to do a task, do some tests which way is more efficient.
    Also if certain JavaScript loops might run for a “longer” time, it can be a good idea to “break up” these loops and use a timer to let the loop run in smaller “junks”. This way the app itself is able to continue to do something else in the background, which would be blocked until the loop has finished otherwise.

  20. Hi Alexander, Thanks for the detailed reply informing me about various steps to solve the slow load problem, i do understand about your situation and so wont flame you for not disclosing the private API’s :).

    As it is the information you have provided will help me tons to solve my client’s problem and hopefully the “checkered pattern” might not be needed too.

    I would have called and said thanks but i just just checked the germany time and its somewhere around midnight over there.

    Cheers dude and keep on making rocking softwares… ps: you have a fan over here to look out for them 🙂

  21. Alexander, thanks for the great writeup!

    One note for 3.x devs, you may have to include the following in MethodSwizzling.h to address a “‘Method’ undeclared (first use in this function)” error:


    Hope that helps someone!

  22. To reiterate, you may need to import: objc/runtime.h and objc/message.h

    Both should be enclosed by angle brackets.

  23. Very interesting. I’ve been trying something similar to change the scale on the request headers. I’m display MJPEG in a UIWebView and the default scaling behavior is a real pain. So far the header tweaking isn’t working for me. Do you know if the default scale can be overridden another way? maybe with a javascript call once the page has loaded?

  24. @Peter
    The scaling can be changed using META tags (see the “Safari Web Content Guide” for the iPhone from Apple, especially the “Customizing the viewport” section). So you can overwrite the default scaling by inserting a META tag into the head section of the HTML code of the web page with the new scaling definition. This can be done via JavaScript (injecting the JavaScript code can be done with the method “stringByEvaluatingJavaScriptFromString:” of the UIWebView class).

  25. @Alexander Thanks for the response! I tried some js viewport manipulation from webViewDidFinishLoad. But this method never gets called (stream), so I tried on a background thread, but the next frame of video seems to overwrite the change. So now I’m thinking I need to somehow intercept the http response to insert the viewport meta change… I’m really hoping I don’t need to parse the mjpeg stream manually, but UIWebView doesn’t seem to expose enough control.

  26. @Peter
    Have you correctly set the delegate for the UIWebView object? If it is never called because the web page never “finishes” to load, then you are in “trouble” 😉 What is the web page you’re having trouble with? Is this really a web page with real HTML code? Or is this a video stream without any HTML code in which it is embedded? If it is the latter, I assume you can’t add a META tag in something that is not HTML.

  27. @Alexander
    Yes that’s what I mean, because it’s a stream, the method doesn’t get called. It’s MJPEG from an IP camera (example: guest,guest). Displays fine, but i can’t find a way to change UIWebView scaling. The back ground thread approach seems to change the scale, but only until the next frame is received. very frustrating.

  28. @Peter
    I assume that UIWebView treats each frame of the stream as new “web page”, so you would need to set the META tag for each frame, and unfortunately this is not possible with the limited API of the iOS.

    But you should try to embed the image stream using the IMG tag within a simple HTML document. This way UIWebView has to deal only with one single HTML document.

  29. Great article.
    Out of curiosity, what prevents us from just creating a category on NSMutableURLRequest and overriding setValue: forHTTPHeaderField:?

  30. From “When a category overrides an inherited method, the method in the category can, as usual, invoke the inherited implementation via a message to super. However, if a category overrides a method that already existed in the category’s class, there is no way to invoke the original implementation”

    In our case the method “setValue: forHTTPHeaderField:” is not an inherited method, and because we would overwrite it in a Category, we would be no longer able to invoke the original implementation. But exactly this is what we need to do here: we either pass the original arguments or the modified User-Agent header to the original implementation. We have to use the original implementation because there’s no other way to access the storage for the headers of the class (the storage is private).

  31. Pingback: UIWebView user-agent weirdness and how to change user-agent value programmatically |

  32. I am trying to set the viewport as follows for

    This is what I do next:

    NSString *path = [[NSBundle mainBundle] pathForResource:@”JScript” ofType:@”js”];
    self.jsScript = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];

    – (void)webViewDidStartLoad:(UIWebView *)webView
    [self.web stringByEvaluatingJavaScriptFromString:self.jsScript];

    – (void)webViewDidFinishLoad:(UIWebView *)webView
    [self.web stringByEvaluatingJavaScriptFromString:self.jsScript];

    But I can still zoom in and zoom out.

    The following are the properties that are set for UIWebView:

    self.web.scalesPageToFit = TRUE;
    self.web.delegate = self;
    self.web.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
    self.web.backgroundColor = [UIColor scrollViewTexturedBackgroundColor];

    When I zoom a little and change the orientation, a big part of the screen becomes void with the background of the webview. Why does this happen.

    Link to Project:

  33. @Kartik Thapar
    The file “JScript.js” from your example contains only one line of HTML code (the META element). But you can not add HTML code into a web page this way.
    The method “stringByEvaluatingJavaScriptFromString:” expects Javascript code, so you have to write JavaScript code which creates a META element and inserts it into the HTML tree at the proper location. You can look at my blog post “Search and highlight text in UIWebView” to get an impression how to create HTML elements using JavaScript and add them into the HTML tree.

  34. Hello Alexander. I have implemented this method and generally it works great. However, some sites still seem to read my user agent as iPad even though I have it swizzled for desktop Safari. For example: still shows the ipad user agent. But shows my desktop Safari user agent (properly). I noticed some sites that use Disqus blogs only treat my UIWebView as if it were an iPad and not desktop Safari. iCab has no problem with this and still reports as Desktop (if selected).

    Is there an additional step that must be taken for Javascript user agent checks (assuming that’s what’s going on here?)


  35. @Kory
    Yes, you’re right. Changing the HTTP header doesn’t have any effect on the JavaScript code.If you need to modify the user agent of JavaScript as well, you have to overwrite the “navigator” object of JavaScript with your own custom object. The “userAgent” property of the default navigator object is read only, so you can’t just overwrite it, but the navigator object itself can be replaced. The Navigator object has some other properties as well, so you may need to add these to your custom navigator object as well.

    All you need to do it to “inject” some JavaScript code which overwrites the default navigator object…

    var myNavigator = new Object();
    myNavigator.userAgent = “new User Agent”;
    myNavigator.appName = “My App”;
    //… add all the other properties which are found in the original navigator object
    navigator = myNavigator; // replace the original ‘navigator’ object

    When checking for the userAgent somewhere else…


    you’ll now get “new User Agent”.

    The only issue is, that you have to change the navigator object as soon as possible after you start loading a new page (before the page load has finished) because some web pages do read the userAgent value while they are still loading.

  36. @Alexander
    I was thinking it had something to do with the javascript navigator object, but did not know I could simply “replace” it inline.
    I injected the javascript in webViewDidStartLoad and it works great!
    Thanks as always for your help.

  37. I’m late to the party, but my understanding from other sites is that registering an NSString user default with key name “UserAgent” has the effect of setting the user agent for all HTTP requests henceforth and changing the result read back by any Javascript, and I’ve experimentally verified that to be the case. However, I’m unable to find any official documentation to suggest that’s an official API feature — has anyone any idea of the safety of using that method? Is it possibly documented somewhere official or carried over from OS X in some way that I’ve yet to spot, or is it probably unofficial?

  38. @Tomby
    Using a special undocumented key for the user preferences is most likely not conforming to the iOS programming guidelines. And I guess it has the disadvantage that you can’t change the UserAgent after the UIWebView object was created. So you would need to delete and
    re-create the UIWebView objects each time you need to change the UserAgent setting(which will also mean that the back/forward history is lost).

  39. Hi, does this method still works in iOS5 ? It seems that it doesn’t work anymore for me, but I’m not sure currently what is the problem. Still searching…

  40. @groumpf
    You’re right, under iOS the UIWebView does no longer create its NSURLRequest objects through the public Cocoa API of this class. The whole technique of method swizzling is still working under iOS 5, but in this case it is of no use anymore, because the UIWebView does no longer call the methods we’ve exchanged.

    The fact that UIWebView is internally no longer using the NSURLRequest API is probably the reason why there are some other serious problems with UIWebView under iOS 5 now. Issues, which can de definitely called bugs, because now UIWebView is really broken for certain tasks.

    For example if you create a NSURLRequest for a POST request where you need to provide an NSInputStream for the data that needs to be posted, and then pass this to an UIWebView instance, then this won’t work anymore. UIWebView forgets to preserve the stream internally when it needs to copy and recreate the original NSURLRequest in order to add its own headers (like UserAgent, cookies etc.). So UIWebView ends up posting an empty request, all the data that should be posted with the request is lost. A serious bug in iOS 5.

    The UserAgent issue is most likely a side effect of this bug, though not really a bug itself.

    The only solution for the problem is much more complicated: Create your own HTTP protocol handler using the NSURLProtocol/NSURLProtocolClient classes.

  41. @Alexander thank for reply
    I’m wondering if another (slightly easier ?) solution could be to execute each request instead of letting the UIWebView doing it. You can then make the request you like, get the result in a string and pass it to the UIWebView loadHTMLString and do the same on shouldLoad…
    Except for the resources, it should work.

  42. @groumpf
    You could do this, but I guess this will not work very well in general. The resources are not covered this way, but these can be important as well. And when you load the main requests yourself and then feed the result to UIWebView as NSData or as string, you lose all the background loading ability or UIWebView. So everything will be much slower.

  43. it seems we have to do this workaround to change User-Agent, not sure Apple allow to do this.
    I just do the test on iPhone Simulator (iOS 5), and it works well so far.
    NSString *userAgent = @"Mozilla/5.0 (iPad; U; CPU OS 4_3_3 like Mac OS X; ja-jp) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8J2 Safari/6533.18.5";

    id webDocumentView;
    id webView;
    webDocumentView = objc_msgSend(mWebView, @selector(_documentView));
    object_getInstanceVariable(webDocumentView, "_webView", (void**)&webView);
    objc_msgSend(webView, @selector(setCustomUserAgent:), userAgent);

  44. @henry yan
    What you’re proposing would use private API. This might be fine for App that are only used by you or in your company or when developing for the “jailbreak” market, but it is definitely not allowed in the AppStore. Apple might reject your App right away if you use the private API, or they might pull it later from the AppStore, when they find out what you’re doing.

    As I noted above in one comment, I assume the only “legal” way to change the UserAgent that also works under iOS 5 is to implement your own HTTP protocol handler using NSURLProtocol, which is a public API.

  45. For my prototype app I don’t care if I have to use a private API, so the solution from henry yan would be perfect. But in my App I’m using ARC and I think mainly this cast “(void**)&webView)” gives an error when trying to compile and using ARC. Does someone know how to change the code from henry to make it compile when using ARC? (I know, I really should study the ARC internals more profoundly…)

  46. hi
    thanks for the great post!
    i am trying to use your approach in ios 5.
    the swizzleMethod is called at runtime, but
    – (void)newSetValue:(NSString *)value forHTTPHeaderField:(NSString *)field
    never gets called at runtime.
    do you know if the uiwebview implementation in ios5 still uses NsMutableUrlRequest ?

    Cheers from Austria

Leave a Reply

Your email address will not be published. Required fields are marked *