Setting a Custom User Agent in Objective-C

Sadly, it is a common scenario in web development to have code that handles specific browsers, or classes of browsers, differently.  The practice largely has it’s roots in the “bad-old-days” of having to handle the many various quirks and idiosyncrasies in the different CSS and layout engines of the major browser vendors.  Thankfully, this problem is getting, largely, better (at least in my experience), and the need to write browser-specific code of this type is becoming less of an issue.

But handling layout differences across browsers isn’t the only reason to treat different clients uniquely.  Dropbox’s matching of the sort order paradigms of either Windows or Mac depending on which OS the site is being viewed on is a practical example of when functional differentiation is desired for different clients.

Dropbox on Mac

Dropbox on Mac

Dropbox on Windows

Dropbox on Windows

These techniques are nearly always implemented by inspecting the user agent string provided in the headers of each request the server receives.  Each browser provides a user agent header that describes the type, version, OS, and other relevant details about the client sending the request.  For a web application, the client application is the web browser, so you don’t need to worry about specifying one, the browser provides it. You only need to worry about consuming it if necessary.

But what about hybrid native applications where mobile content is running within a web browser control (such as UIWebView in iOS) within a native application.  By default, the web browser control typically sends a subset of the user agent string the full-browser version would send.  But what if you want your server code to recognize when your native application is submitting requests?  How can you specify details about the native application in the user agent string if you need the backend application to behave differently for these hybrid clients?

In the iOS SDK, the answer is to modify the UIWebView’s user agent string for your application.  This is easily done by:

– (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSString *existingUserAgent = [[[UIWebView alloc] init] stringByEvaluatingJavaScriptFromString:@”navigator.userAgent”];

NSString *newUserAgent = [NSString stringWithFormat:@”%@ custom-information-about-my-application”, existingUserAgent];
NSDictionary *userDefaults = [[NSDictionary alloc] initWithObjectsAndKeys:newUserAgent, @”UserAgent”, nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:userDefaults];

/* … your application code … */

}

The essence of the above code is that the existing UIWebView user agent is retrieved from a UIWebView instance (it doesn’t need to be the instance that will actually display the content), the custom user agent information is appended to the original user agent, and then the new user agent is registered to the user defaults of the application under the key UserAgent.  This will set the user agent sent for all network requests sent from your application’s code, including raw NSConnection requests.*

In my experience, you should always augment the existing user agent rather than completely replace it as the server will likely make some assumptions about whether you are a supported client and what flavor of display code to deliver based on the web view control’s existing agent string.  This is especially true in large applications where you don’t own every aspect of the server code.

* NOTE:  Things can get a little weird for requests issued from a linked library (like making an API call such as stringWithContentsOfFile).  Therefore, it is dangerous to assume that EVERY server connection issued by your application will carry the custom user agent. Your milage may vary, so verify that the custom agent is being applied uniformly for all calls and adjust how the agent is registered if needed.

Leave a Reply