These methods extend NSString, NSURL, and NSDictionary, to allow you to convert to and from query components strings and dictionary objects containing the results.
Now the related .m code:
#import "XQueryComponents.h"
@implementation NSString (XQueryComponents)
- (NSString *)stringByDecodingURLFormat
{
NSString *result = [self stringByReplacingOccurrencesOfString:@"+" withString:@" "];
result = [result stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
return result;
}
- (NSString *)stringByEncodingURLFormat
{
NSString *result = [self stringByReplacingOccurrencesOfString:@" " withString:@"+"];
result = [result stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
return result;
}
- (NSMutableDictionary *)dictionaryFromQueryComponents
{
NSMutableDictionary *queryComponents = [NSMutableDictionary dictionary];
for(NSString *keyValuePairString in [self componentsSeparatedByString:@"&"])
{
NSArray *keyValuePairArray = [keyValuePairString componentsSeparatedByString:@"="];
if ([keyValuePairArray count] < 2) continue; // Verify that there is at least one key, and at least one value. Ignore extra = signs
NSString *key = [[keyValuePairArray objectAtIndex:0] stringByDecodingURLFormat];
NSString *value = [[keyValuePairArray objectAtIndex:1] stringByDecodingURLFormat];
NSMutableArray *results = [queryComponents objectForKey:key]; // URL spec says that multiple values are allowed per key
if(!results) // First object
{
results = [NSMutableArray arrayWithCapacity:1];
[queryComponents setObject:results forKey:key];
}
[results addObject:value];
}
return queryComponents;
}
@end
@implementation NSURL (XQueryComponents)
- (NSMutableDictionary *)queryComponents
{
return [[self query] dictionaryFromQueryComponents];
}
@end
@implementation NSDictionary (XQueryComponents)
- (NSString *)stringFromQueryComponents
{
NSString *result = nil;
for(__strong NSString *key in [self allKeys])
{
key = [key stringByEncodingURLFormat];
NSArray *allValues = [self objectForKey:key];
if([allValues isKindOfClass:[NSArray class]])
for(__strong NSString *value in allValues)
{
value = [[value description] stringByEncodingURLFormat];
if(!result)
result = [NSString stringWithFormat:@"%@=%@",key,value];
else
result = [result stringByAppendingFormat:@"&%@=%@",key,value];
}
else {
NSString *value = [[allValues description] stringByEncodingURLFormat];
if(!result)
result = [NSString stringWithFormat:@"%@=%@",key,value];
else
result = [result stringByAppendingFormat:@"&%@=%@",key,value];
}
}
return result;
}
@end
extension NSURL{
func queryParams() -> [String:AnyObject] {
var info : [String:AnyObject] = [String:AnyObject]()
if let queryString = self.query{
for parameter in queryString.componentsSeparatedByString("&"){
let parts = parameter.componentsSeparatedByString("=")
if parts.count > 1{
let key = (parts[0] as String).stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
let value = (parts[1] as String).stringByReplacingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
if key != nil && value != nil{
info[key!] = value
}
}
}
}
return info
}
}
When you get this property’s value, the NSURLComponents class parses the query string and returns an array of NSURLQueryItem objects, each of which represents a single key-value pair, in the order in which they appear in the original query string.
Swift
let url = "http://example.com?param1=value1¶m2=param2"
let queryItems = URLComponents(string: url)?.queryItems
let param1 = queryItems?.filter({$0.name == "param1"}).first
print(param1?.value)
Alternatively, you can add an extension on URL to make things easier.
Hendrik wrote a nice example for extension in this question, however I had to re-write it to not use any objective-c library methods. Using NSArray in swift is not the correct approach.
This is the result, all swift and a bit more safe. The usage example will be less lines of code with Swift 1.2.
public extension NSURL {
/*
Set an array with all the query items
*/
var allQueryItems: [NSURLQueryItem] {
get {
let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false)!
if let allQueryItems = components.queryItems {
return allQueryItems as [NSURLQueryItem]
} else {
return []
}
}
}
/**
Get a query item form the URL query
:param: key The parameter to fetch from the URL query
:returns: `NSURLQueryItem` the query item
*/
public func queryItemForKey(key: String) -> NSURLQueryItem? {
let filteredArray = filter(allQueryItems) { $0.name == key }
if filteredArray.count > 0 {
return filteredArray.first
} else {
return nil
}
}
}
Usage:
let queryItem = url.queryItemForKey("myItem")
Or, more detailed usage:
if let url = NSURL(string: "http://www.domain.com/?myItem=something") {
if let queryItem = url.queryItemForKey("myItem") {
if let value = queryItem.value {
println("The value of 'myItem' is: \(value)")
}
}
}
extension NSURL {
/**
* URL query string as dictionary. Empty dictionary if query string is nil.
*/
public var queryValues : [String:String] {
get {
if let q = self.query {
return q.componentsSeparatedByString("&").map {
$0.componentsSeparatedByString("=")
}.reduce([:]) {
(var dict: [String:String], p) in
dict[p[0]] = p[1]
return dict
}
} else {
return [:]
}
}
}
}
Example:
let url = NSURL(string: "http://example.com?p1=v1&p2=v2")!
let queryDict = url.queryValues
// ["p1": "v1", "p2": "v2"]
Please note, if using OS X 10.10 or iOS 8 (or later), it's probably better to use NSURLComponents and the queryItems property and create the dictionary from the NSURLQueryItems directly.
Here's a NSURLComponents based NSURL extension solution:
extension NSURL {
/// URL query string as a dictionary. Empty dictionary if query string is nil.
public var queryValues : [String:String] {
get {
guard let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false) else {
return [:]
}
guard let queryItems = components.queryItems else {
return [:]
}
var result:[String:String] = [:]
for q in queryItems {
result[q.name] = q.value
}
return result
}
}
}
A footnote to the NSURL extension is that it's actually possible in Swift to give the property the same name as the existing string property—query. I didn't know until I tried it, but the polymorphism in Swift lets you differ only on the return type. So if the extended NSURL property is public var query: [String:String] it works. I didn't use this in the example as I find it a little bit crazy, but it does work ...
Caveat: URLs can have repeated params, but the dictionary will only contain the last value of any duplicated param. If that is undesirable, use the queryItems array directly.