URL encoding an NSString on iOS

I’ve been working on an iPhone app for the last few weeks, which I’ve really enjoyed. Every now and again, though, you hit what seems like a bug in the iOS SDK.

 This seems to happen much more frequently than it ever did when I was coding in C#. As a result, my default debugging approach – that any problem with my app must be my fault rather than something in the framework – has shifted slightly. I’m now much more likely to question the framework itself, and with a quick Google search it’s common to find other developers who have experienced the same problem.

Here’s one that bit me recently. NSString has a method calledstringByAddingPercentEscapesUsingEncoding, which purports to make the string safe for use, say, as a parameter to a URL. There are several characters that are reserved in parameters toURLs – for example the slash character, or the ampersand, because these are characters that are used to delimit the URL itself. Therefore we encode these, and this is done using the percent encoding scheme.

As an example, if you want to use the string hell & brimstone + earthly/delight (and why wouldn’t you?) as a parameter to a URL then you’ll need to convert it to hell+%26+brimstone+%2B+earthly%2Fdelight so that the ampersand becomes %26, the plus becomes %2B and the slash becomes %2F. Note that spaces are encoded as pluses, which is why the original plus sign needs to be encoded.

But stringByAddingPercentEscapesUsingEncoding doesn’t respect these rules, and actually produces this: hell%20&%20brimstone%20+%20earthly/delight

It fails to encode the ampersand, the slash and the plus, and many (most?) web servers will be confused by that. It encodes the spaces as %20, which is just as acceptable as encoding them as a plus.

In short, it’s completely broken, which is frustrating. But thankfully there’s a lower-level API we can use which, thanks to the magic of Objective-C categories, we can tack on to NSString. This is based on a blogpost by Simon Woodside, which I’ve just turned into a category.

Here’s the category’s header file:

#import <Foundation/Foundation.h>
@interface NSString (URLEncoding)
-(NSString *)urlEncodeUsingEncoding:(NSStringEncoding)encoding;
@end

And here’s the implementation:

#import "NSString+URLEncoding.h"
@implementation NSString (URLEncoding)
-(NSString *)urlEncodeUsingEncoding:(NSStringEncoding)encoding {
	return (NSString *)CFURLCreateStringByAddingPercentEscapes(NULL,
			   (CFStringRef)self,
			   NULL,
			   (CFStringRef)@"!*'\"();:@&=+$,/?%#[]% ",
			   CFStringConvertNSStringEncodingToEncoding(encoding));
}
@end

And now we can simply do this:

NSString *raw = @"hell & brimstone + earthly/delight";
NSString *url = [NSString stringWithFormat:@"http://example.com/example?param=%@",
			[raw urlEncodeUsingEncoding:NSUTF8Encoding]];
NSLog(url);

And the result will be exactly as we hoped:

http://example.com/example?param=hell%20%26%20brimstone%20%2B%20earthly%2Fdelight

7 comments

Author:  PeteMagsig

Thanks. This is exactly what I was looking for.

I have the same feelings, too, about C# and Objective-C. It seems that I spend a lot of time mucking about and questioning the underlying iOS framework than I do with .Net.

Tweet this
Author:  InfiniteHoops

Hey, thanks for writing this up. I’ve also been surprised a few things in System.Web that I use all the time do not have equivalents built into iOS.

Tweet this
Author:  neil_young

You don’t need to do it in a category. Just call the function:

NSString escapedString = (NSString *)CFURLCreateStringByAddingPercentEscapes( NULL, (CFStringRef)yourInput, NULL, (CFStringRef)@"!’\"();:@&=+$,/?%#[]% ", kCFStringEncodingISOLatin1);

in order to achieve the same. However, you have to [escapedString release] in both cases in order to not leak

Tweet this
Author: James Higgs James Higgs

@Neil_young well, no, you don’t need the category. You never really need categories, but I find that they make my code a little bit more readable, and more DRY. Both good things as far as I’m concerned.

Tweet this