86

I have a URL like myApp://action/1?parameter=2&secondparameter=3

With the property query I get following part of my URL

parameter=2&secondparameter=3

Is there any way easy to put this in a NSDictionary or an Array?

Thx a lot

Rafał Sroka
  • 39,540
  • 23
  • 113
  • 143
gabac
  • 2,062
  • 6
  • 21
  • 30

17 Answers17

152

You can use queryItems in URLComponents.

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&param2=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.

extension URL {
    var queryParameters: QueryParameters { return QueryParameters(url: self) }
}

class QueryParameters {
    let queryItems: [URLQueryItem]
    init(url: URL?) {
        queryItems = URLComponents(string: url?.absoluteString ?? "")?.queryItems ?? []
        print(queryItems)
    }
    subscript(name: String) -> String? {
        return queryItems.first(where: { $0.name == name })?.value
    }
}

You can then access the parameter by its name.

let url = "http://example.com?param1=value1&param2=param2"
print(url.queryParameters["param1"])
Onato
  • 9,916
  • 5
  • 46
  • 54
  • 11
    query since iOS7. queryItems since iOS8. – Onato Dec 24 '14 at 10:06
  • Thanks for this answer. In addition to this, I found the following article very helpful to learn more about NSURLComponents: http://nshipster.com/nsurl/ – xaphod Oct 30 '15 at 10:57
  • Strangely, NSURLComponents doesn't work if there's # character in the URL. Like `http://example.com/abc#abc?param1=value1&param2=param2` queryItems will be nil – Pedro Oct 05 '17 at 19:48
  • That is because the fragment (#abc) belongs at the end. – Onato Oct 05 '17 at 19:51
  • It works just great with Swift 4.2 as well. I could just remove an URL extension that felt very out of place. – Michele Dall'Agata Jul 10 '18 at 21:50
55

I had reason to write some extensions for this behavior that might come in handy. First the header:

#import <Foundation/Foundation.h>

@interface NSString (XQueryComponents)
- (NSString *)stringByDecodingURLFormat;
- (NSString *)stringByEncodingURLFormat;
- (NSMutableDictionary *)dictionaryFromQueryComponents;
@end

@interface NSURL (XQueryComponents)
- (NSMutableDictionary *)queryComponents;
@end

@interface NSDictionary (XQueryComponents)
- (NSString *)stringFromQueryComponents;
@end

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
James Billingham
  • 760
  • 8
  • 32
BadPirate
  • 25,802
  • 10
  • 92
  • 123
  • two problems: 1) you have to URL decode both the key and the value before saving them in to the dictionary and 2) according to the rules of URLs, you're allowed to specify the same key multiple times. IOW, it should be a key mapped to an array of values, not to a single value. – Dave DeLong Mar 23 '11 at 19:47
  • Ok, you just add a check on array size (is that require a down vote on my answer ?) ! This code was just a start (as always on SO). If you want to be compliant with all URL, you have more works to treat escape strings, fragment (# part at the end),... – Benoît Mar 24 '11 at 06:47
  • Dave - Good points. Wasn't required for my needs, but since I'm trying to make an answer that will work for everyone visiting on this common problem I have included the mentioned functionality... Benoit - Just calling your attention to the issue with your answer, I submitted an edit. Probably not the best form to use down to call attention to minor issues in otherwise good answers, I'll refrain from doing so in the future.. Can't change the down vote until an edit goes through though :/ – BadPirate Mar 31 '11 at 23:14
  • In stringByDecodingURLFormat the type for 'result' should be NSString* . – ThomasW Apr 05 '11 at 08:36
  • Just noticed another issue. The key and value are getting mixed up, the key should be the objectAtIndex:0 and the value the objectAtIndex:1. – ThomasW Apr 05 '11 at 08:46
  • I've corrected those, and added some more functionality. Actually had reason to use this code, so I spruced it up a bit and verified it works :) – BadPirate Apr 07 '11 at 20:02
  • Hey, BadPirate, why didn't you place this nice code somewhere on github? – Stanislav Pankevich Aug 20 '12 at 23:21
  • Stanislaw - Haven't had any requests for people who want to collaborate and add onto the code :) Though I have a number of little fixes like this, thought it might be worthwhile to turn them into a library that other people can use (Like TT, etc). – BadPirate Aug 21 '12 at 23:52
54

Something like that:

NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
for (NSString *param in [url componentsSeparatedByString:@"&"]) {
  NSArray *elts = [param componentsSeparatedByString:@"="];
  if([elts count] < 2) continue;
  [params setObject:[elts lastObject] forKey:[elts firstObject]];
}

Note : This is sample code. All error cases are not managed.

Benoît
  • 7,395
  • 2
  • 25
  • 30
  • 2
    This code will crash in the case of a malformed query. See safe solution below. – BadPirate Mar 23 '11 at 19:06
  • 2
    Ok, you just add a check on array size ! This code was just a start (as always on SO). If you want to be compliant with all URL, you have more works to treat escape strings, fragment (# part at the end),... – Benoît Mar 24 '11 at 06:46
  • Put in an edit for you. Only down voted to bring your attention to the issue with the answer. Once the edit has gone through I'll be able to bring it back up. – BadPirate Mar 31 '11 at 22:55
  • 1
    This code isn't even close to correct except for the simplest case values with no special characters. – Nick Snyder Sep 18 '13 at 17:21
  • You should use last & first object instead: `[params setObject:[elts lastObject] forKey:[elts firstObject]];` its more secure – Laszlo Oct 01 '15 at 10:27
  • This one will return errorneous array with http://example.com/a?b which is a valid query too. – Rilakkuma Mar 03 '16 at 10:28
  • The correct way to do this is using NSURLComponents - see answer below. @gabac please correct the accepted answer. – siburb Jun 02 '17 at 02:01
14

Try this ;)!

NSString *query = @"parameter=2&secondparameter=3"; // replace this with [url query];
NSArray *components = [query componentsSeparatedByString:@"&"];
NSMutableDictionary *parameters = [[NSMutableDictionary alloc] init];
for (NSString *component in components) {
    NSArray *subcomponents = [component componentsSeparatedByString:@"="];
    [parameters setObject:[[subcomponents objectAtIndex:1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]
                   forKey:[[subcomponents objectAtIndex:0] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
}
raidfive
  • 6,603
  • 1
  • 35
  • 32
cutsoy
  • 10,127
  • 4
  • 40
  • 57
  • 1
    Also note -- beyond the crash issue noted by BadPirate -- (1) that the dictionary key/values are reversed; (2) the key/values are still encoded so you want to do stringByDecodingURLFormat – dpjanes Apr 10 '11 at 15:19
  • Really nice way of doing it – Burf2000 Sep 25 '13 at 13:36
9

All previous posts do not do the url encoding properly. I would suggest the following methods:

+(NSString*)concatenateQuery:(NSDictionary*)parameters {
    if([parameters count]==0) return nil;
    NSMutableString* query = [NSMutableString string];
    for(NSString* parameter in [parameters allKeys])
        [query appendFormat:@"&%@=%@",[parameter stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet],[[parameters objectForKey:parameter] stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]];
    return [[query substringFromIndex:1] copy];
}
+(NSDictionary*)splitQuery:(NSString*)query {
    if([query length]==0) return nil;
    NSMutableDictionary* parameters = [NSMutableDictionary dictionary];
    for(NSString* parameter in [query componentsSeparatedByString:@"&"]) {
        NSRange range = [parameter rangeOfString:@"="];
        if(range.location!=NSNotFound)
            [parameters setObject:[[parameter substringFromIndex:range.location+range.length] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] forKey:[[parameter substringToIndex:range.location] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
        else [parameters setObject:[[NSString alloc] init] forKey:[parameter stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
    }
    return [parameters copy];
}
Kristian Kraljic
  • 826
  • 12
  • 11
  • 1
    `if(!query||[query length] == 0)` is redundant. You can safely check `if([query length] == 0)` as in event that `query == nil`, it will return value of `0` if you try calling length on it. – JRG-Developer Apr 02 '13 at 17:29
  • shouldn't this be using objectForKey and not valueforkey? – slf Sep 24 '13 at 18:47
  • Nice code. I suggest using `NSUTF8StringEncoding` in `splitQuery` to support all charsets :) Also, `[string stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]` is a newer way of encoding for `concatenateQuery`. – William Denniss Mar 23 '14 at 13:06
  • I think it's good practice to return an immutable instance, so maybe you should change the last line to return [parameters copy]; – Glenn Howes May 14 '14 at 14:27
  • Thanks. I have adapted the code according to your comments! – Kristian Kraljic Jun 12 '15 at 21:40
8

According to the already very clean answer of Onato I wrote an extension for NSURL in Swift where you can get a query param like this:

e.g. the URL contains the pair param=some_value

let queryItem = url.queryItemForKey("param")
let value = queryItem.value // would get String "someValue"

The extension looks like:

extension NSURL {

  var allQueryItems: [NSURLQueryItem] {
      get {
          let components = NSURLComponents(URL: self, resolvingAgainstBaseURL: false)!
          let allQueryItems = components.queryItems!
          return allQueryItems as [NSURLQueryItem]
      }
  }

  func queryItemForKey(key: String) -> NSURLQueryItem? {

      let predicate = NSPredicate(format: "name=%@", key)!
      return (allQueryItems as NSArray).filteredArrayUsingPredicate(predicate).first as? NSURLQueryItem

  }
}
Community
  • 1
  • 1
Hendrik
  • 940
  • 8
  • 14
7

Here is the extension in swift:

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
        }
    }
mukaissi
  • 2,441
  • 1
  • 21
  • 12
6

The preferred way to deal with URLs is now NSURLComponents. In particular the queryItems property which returns an NSArray of params.

If you want the params in a NSDictionary, here's a method:

+(NSDictionary<NSString *, NSString *>*)queryParamsFromURL:(NSURL*)url
{
    NSURLComponents* urlComponents = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];

    NSMutableDictionary<NSString *, NSString *>* queryParams = [NSMutableDictionary<NSString *, NSString *> new];
    for (NSURLQueryItem* queryItem in [urlComponents queryItems])
    {
        if (queryItem.value == nil)
        {
            continue;
        }
        [queryParams setObject:queryItem.value forKey:queryItem.name];
    }
    return queryParams;
}

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.

William Denniss
  • 16,089
  • 7
  • 81
  • 124
5

For those using Bolts Framework you can use:

NSDictionary *parameters = [BFURL URLWithURL:yourURL].inputQueryParameters;

Remember to import:

#import <Bolts/BFURL.h>

If you happen to have Facebook SDK in your project, you also have Bolts. Facebook is using this framework as a dependency.

Rafał Sroka
  • 39,540
  • 23
  • 113
  • 143
  • In Swift with Bolts: `BFURL(url: url)?.inputQueryParameters?["myKey"] as? String` – JAL Oct 21 '16 at 16:01
4

Swift 2.1

Oneliner:

"p1=v1&p2=v2".componentsSeparatedByString("&").map {
    $0.componentsSeparatedByString("=") 
}.reduce([:]) {
    (var dict: [String:String], p) in
    dict[p[0]] = p[1]
    return dict
}

// ["p1": "v1", "p2": "v2"]

Used as an extension on NSURL:

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 ...

Erik Tjernlund
  • 1,963
  • 13
  • 12
2

I published a simple class doing the job under MIT:

https://github.com/anegmawad/URLQueryToCocoa

With it you can have arrays and objects in the query, which are collected and glued together

For Example

users[0][firstName]=Amin&users[0][lastName]=Negm&name=Devs&users[1][lastName]=Kienle&users[1][firstName]=Christian

will become:

@{
   name : @"Devs",
   users :
   @[
      @{
         firstName = @"Amin",
         lastName = @"Negm"
      },
      @{
         firstName = @"Christian",
         lastName = @"Kienle"
      }
   ]
 }

You can think of it as a URL query counterpart of NSJSONSerializer.

Amin Negm-Awad
  • 16,582
  • 3
  • 35
  • 50
2

It looks that you are using it to process incoming data from another iOS application. If so, this is what I use for the same purpose.

Initial call (e.g. in external application):

UIApplication *application = [UIApplication sharedApplication];
NSURL *url = [NSURL URLWithString:@"myApp://action/1?parameter=2&secondparameter=3"];
if ([application canOpenURL:url]) {
    [application openURL:url];
    NSLog(@"myApp is installed");
} else {
    NSLog(@"myApp is not installed");
}

Method to extract QueryString data from NSURL and save as NSDictionary:

-(NSDictionary *) getNSDictionaryFromQueryString:(NSURL *)url {
   NSMutableDictionary *result = [[NSMutableDictionary alloc] init];
   NSRange needle = [url.absoluteString rangeOfString:@"?" options:NSCaseInsensitiveSearch];
   NSString *data = nil;

   if(needle.location != NSNotFound) {
       NSUInteger start = needle.location + 1;
       NSUInteger end = [url.absoluteString length] - start;
       data = [url.absoluteString substringWithRange:NSMakeRange(start, end)];
   }

   for (NSString *param in [data componentsSeparatedByString:@"&"]) {
       NSArray *keyvalue = [param componentsSeparatedByString:@"="];
       if([keyvalue count] == 2){
           [result setObject:[keyvalue objectAtIndex:1] forKey:[keyvalue objectAtIndex:0]];
       }
   }

  return result;
}

Usage:

NSDictionary *result = [self getNSDictionaryFromQueryString:url];
Gillsoft AB
  • 4,185
  • 2
  • 19
  • 35
0

This class is a nice solution for url parsing.

.h file

@interface URLParser : NSObject {
    NSArray *variables;
}

@property (nonatomic, retain) NSArray *variables;

- (id)initWithURLString:(NSString *)url;
- (NSString *)valueForVariable:(NSString *)varName;

@end

.m file

#import "URLParser.h"

@implementation URLParser
@synthesize variables;

- (id) initWithURLString:(NSString *)url{
    self = [super init];
    if (self != nil) {
        NSString *string = url;
        NSScanner *scanner = [NSScanner scannerWithString:string];
        [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@"&?"]];
        NSString *tempString;
        NSMutableArray *vars = [NSMutableArray new];
        [scanner scanUpToString:@"?" intoString:nil];       //ignore the beginning of the string and skip to the vars
        while ([scanner scanUpToString:@"&" intoString:&tempString]) {
            [vars addObject:[tempString copy]];
        }
        self.variables = vars;
    }
    return self;
}

- (NSString *)valueForVariable:(NSString *)varName {
    for (NSString *var in self.variables) {
        if ([var length] > [varName length]+1 && [[var substringWithRange:NSMakeRange(0, [varName length]+1)] isEqualToString:[varName stringByAppendingString:@"="]]) {
            NSString *varValue = [var substringFromIndex:[varName length]+1];
            return varValue;
        }
    }
    return nil;
}

@end
ymutlu
  • 6,585
  • 4
  • 35
  • 47
0

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)")
        }
    }
}
Community
  • 1
  • 1
Paul Peelen
  • 10,073
  • 15
  • 85
  • 168
0

try this:

-(NSDictionary *)getUrlParameters:(NSString *)url{
    NSArray *justParamsArr = [url componentsSeparatedByString:@"?"];
    url = [justParamsArr lastObject];
    NSMutableDictionary *params = [[NSMutableDictionary alloc] init];
    for (NSString *param in [url componentsSeparatedByString:@"&"]) {
        NSArray *elts = [param componentsSeparatedByString:@"="];
        if([elts count] < 2) continue;
        [params setObject:[elts lastObject] forKey:[elts firstObject]];
    }
    return params;
}
reza_khalafi
  • 6,230
  • 7
  • 56
  • 82
0

Fairly compact approach:

    func stringParamsToDict(query: String) -> [String: String] {
        let params = query.components(separatedBy: "&").map {
            $0.components(separatedBy: "=")
        }.reduce(into: [String: String]()) { dict, pair in
            if pair.count == 2 {
                dict[pair[0]] = pair[1]
            }
        }
        return params
    }
possen
  • 8,596
  • 2
  • 39
  • 48
-6

Most robust solution if you are using a URL to pass data from the web app to the phone and you want to pass arrays, numbers, strings, ...

JSON encode your object in PHP

header("Location: myAppAction://".urlencode(json_encode($YOUROBJECT)));

And JSON decode the result in iOS

NSData *data = [[[request URL] host] dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *packed = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
William Entriken
  • 37,208
  • 23
  • 149
  • 195