开发者

Parse NSURL query property

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开发者_如何学C put this in a NSDictionary or an Array?

Thx a lot


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"])


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


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.


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]];
}


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];
}


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

  }
}


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


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.


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.


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


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.


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];


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


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)")
        }
    }
}


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;
}


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
    }


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];
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜