Analizar la propiedad de consulta NSURL

82

Tengo una URL como myApp://action/1?parameter=2&secondparameter=3

Con la consulta de propiedad obtengo siguiendo parte de mi URL

parameter=2&secondparameter=3

¿Hay alguna forma fácil de poner esto en un NSDictionaryo en un Array?

Muchas gracias

gabac
fuente

Respuestas:

54

Algo como eso:

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

Nota : Este es un código de muestra. No se gestionan todos los casos de error.

Benoît
fuente
2
Este código fallará en el caso de una consulta con formato incorrecto. Vea la solución segura a continuación.
BadPirate
2
Ok, ¡solo agrega una verificación en el tamaño de la matriz! Este código fue solo un comienzo (como siempre en SO). Si desea cumplir con todas las URL, tiene más trabajos para tratar cadenas de escape, fragmento (# parte al final), ...
Benoît
Realice una edición para usted. Solo votó en contra para llamar su atención sobre el problema con la respuesta. Una vez que se haya realizado la edición, podré volver a activarla.
BadPirate
1
Este código ni siquiera está cerca de ser correcto, excepto por los valores de caso más simples sin caracteres especiales.
Nick Snyder
Debería usar el último y el primer objeto en [params setObject:[elts lastObject] forKey:[elts firstObject]];su lugar: es más seguro
Laszlo
147

Puede usar queryItemsen URLComponents.

Cuando obtiene el valor de esta propiedad, la clase NSURLComponents analiza la cadena de consulta y devuelve una matriz de objetos NSURLQueryItem, cada uno de los cuales representa un único par clave-valor, en el orden en que aparecen en la cadena de consulta original.

Rápido

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)

Alternativamente, puede agregar una extensión en la URL para facilitar las cosas.

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

A continuación, puede acceder al parámetro por su nombre.

let url = "http://example.com?param1=value1&param2=param2"
print(url.queryParameters["param1"])
Onato
fuente
11
consulta desde iOS7. queryItems desde iOS8.
Onato
Gracias por esta respuesta. Además de esto, encontré el siguiente artículo muy útil para aprender más sobre NSURLComponents: nshipster.com/nsurl
xaphod
Gran respuesta. ¡Gracias!
dpigera
Curiosamente, NSURLComponents no funciona si hay un carácter # en la URL. Como http://example.com/abc#abc?param1=value1&param2=param2 queryItems será nulo
Pedro
Eso es porque el fragmento (#abc) pertenece al final.
Onato
55

Tenía motivos para escribir algunas extensiones para este comportamiento que podrían resultar útiles. Primero el encabezado:

#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

Estos métodos amplían NSString, NSURL y NSDictionary, para permitirle convertir hacia y desde cadenas de componentes de consulta y objetos de diccionario que contienen los resultados.

Ahora el código .m relacionado:

#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
BadPirate
fuente
Dos problemas: 1) tienes que decodificar URL tanto la clave como el valor antes de guardarlos en el diccionario y 2) de acuerdo con las reglas de las URL, puedes especificar la misma clave varias veces. IOW, debe ser una clave asignada a una matriz de valores, no a un solo valor.
Dave DeLong
Ok, solo agrega una marca en el tamaño de la matriz (¿eso requiere un voto negativo en mi respuesta?). Este código fue solo un comienzo (como siempre en SO). Si desea cumplir con todas las URL, tiene más trabajos para tratar cadenas de escape, fragmento (# parte al final), ...
Benoît
Dave: Buenos puntos. No era necesario para mis necesidades, pero como estoy tratando de dar una respuesta que funcione para todos los que visitan este problema común, he incluido la funcionalidad mencionada ... Benoit: solo llamo su atención sobre el problema con su respuesta, Envié una edición. Probablemente no sea la mejor forma para llamar la atención sobre problemas menores en respuestas que por lo demás son buenas, me abstendré de hacerlo en el futuro. No puedo cambiar el voto
negativo
En stringByDecodingURLFormat, el tipo de 'resultado' debe ser NSString *.
ThomasW
Acabo de notar otro problema. La clave y el valor se confunden, la clave debe ser objectAtIndex: 0 y el valor objectAtIndex: 1.
ThomasW
13

Prueba esto ;)!

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]];
}
elslooo
fuente
1
También tenga en cuenta, más allá del problema de bloqueo observado por BadPirate, (1) que las claves / valores del diccionario están invertidos; (2) las claves / valores todavía están codificados, por lo que desea hacer stringByDecodingURLFormat
dpjanes
Una forma realmente agradable de hacerlo
Burf2000
9

Todas las publicaciones anteriores no codifican correctamente la URL. Sugeriría los siguientes métodos:

+(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
fuente
if(!query||[query length] == 0)es redundante. Puede verificar con seguridad if([query length] == 0)como en el caso de que query == nildevuelva el valor de 0si intenta llamar a length en él.
JRG-Developer
¿No debería usar objectForKey y no valueforkey?
slf
Buen código. Sugiero usar NSUTF8StringEncodingin splitQuerypara admitir todos los conjuntos de caracteres :) Además, [string stringByAddingPercentEncodingWithAllowedCharacters:NSCharacterSet.URLQueryAllowedCharacterSet]es una forma más nueva de codificar concatenateQuery.
William Denniss
Creo que es una buena práctica devolver una instancia inmutable, por lo que tal vez debería cambiar la última línea para devolver [copia de parámetros];
Glenn Howes
Gracias. ¡He adaptado el código según sus comentarios!
Kristian Kraljic
7

Aquí está la extensión en rápido:

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

De acuerdo con la respuesta ya muy limpia de Onato , escribí una extensión para NSURL en Swift donde puede obtener un parámetro de consulta como este:

por ejemplo, la URL contiene el par param = some_value

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

La extensión se parece a:

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

  }
}
Hendrik
fuente
¡Esta es la mejor respuesta! ¿Por qué no comprobaron los documentos de la API?
Jamin Zhang
5

Para aquellos que usan Bolts Framework , puede usar:

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

Recuerde importar:

#import <Bolts/BFURL.h>

Si tiene Facebook SDK en su proyecto, también tiene Bolts . Facebook está utilizando este marco como dependencia.

RaffAl
fuente
En Swift with Bolts:BFURL(url: url)?.inputQueryParameters?["myKey"] as? String
JAL
4

La forma preferida de tratar las URL es ahora NSURLComponents. En particular, la queryItemspropiedad que devuelve un NSArrayde params.

Si quieres los parámetros en a NSDictionary, aquí tienes un método:

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

Advertencia: las URL pueden tener parámetros repetidos, pero el diccionario solo contendrá el último valor de cualquier parámetro duplicado. Si eso no es deseable, use la queryItemsmatriz directamente.

William Denniss
fuente
3

Swift 2.1

Un trazador de líneas:

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

Usado como una extensión en 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 [:]
            }
        }
    }

}

Ejemplo:

let url = NSURL(string: "http://example.com?p1=v1&p2=v2")!
let queryDict = url.queryValues

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

Tenga en cuenta que si usa OS X 10.10 o iOS 8 (o posterior), probablemente sea mejor usar NSURLComponentsy la queryItemspropiedad y crear el diccionario NSURLQueryItemsdirectamente desde.

Aquí hay una solución de extensión NSURLComponentsbasada NSURL:

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

}

Una nota al pie de la extensión NSURL es que en realidad es posible en Swift darle a la propiedad el mismo nombre que la propiedad de cadena existente— query. No lo supe hasta que lo probé, pero el polimorfismo en Swift te permite diferenciar solo en el tipo de retorno. Entonces, si la propiedad NSURL extendida es public var query: [String:String], funciona. No utilicé esto en el ejemplo porque lo encuentro un poco loco, pero funciona ...

Erik Tjernlund
fuente
2

Publiqué una clase simple haciendo el trabajo bajo MIT:

https://github.com/anegmawad/URLQueryToCocoa

Con él puede tener matrices y objetos en la consulta, que se recopilan y pegan juntos

Por ejemplo

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

se convertirá:

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

Puede considerarlo como una contraparte de consulta de URL de NSJSONSerializer.

Amin Negm-Awad
fuente
excelente trabajo. Esta parece ser la única respuesta correcta (la que tiene en cuenta los valores de matriz y dictado).
Anton Tropashko
2

Parece que lo está utilizando para procesar datos entrantes de otra aplicación de iOS. Si es así, esto es lo que uso para el mismo propósito.

Llamada inicial (por ejemplo, en una aplicación externa):

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

Método para extraer datos QueryString de NSURL y guardarlos como 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;
}

Uso:

NSDictionary *result = [self getNSDictionaryFromQueryString:url];
Gillsoft AB
fuente
0

Esta clase es una buena solución para el análisis de URL.

archivo .h

@interface URLParser : NSObject {
    NSArray *variables;
}

@property (nonatomic, retain) NSArray *variables;

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

@end

archivo .m

#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
fuente
0

Hendrik escribió un buen ejemplo de extensión en esta pregunta, sin embargo, tuve que volver a escribirlo para no usar ningún método de biblioteca de objetivo-c. Usar NSArrayrápido no es el enfoque correcto.

Este es el resultado, todo rápido y un poco más seguro. El ejemplo de uso será menos líneas de código con 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
        }
    }
}

Uso:

let queryItem = url.queryItemForKey("myItem")

O, uso más detallado:

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)")
        }
    }
}
Paul Peelen
fuente
0

prueba esto:

-(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
fuente
0

Enfoque bastante compacto:

    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
fuente
-5

La solución más robusta si está utilizando una URL para pasar datos desde la aplicación web al teléfono y desea pasar matrices, números, cadenas, ...

JSON codifica tu objeto en PHP

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

Y JSON decodifica el resultado en iOS

NSData *data = [[[request URL] host] dataUsingEncoding:NSUTF8StringEncoding];
NSDictionary *packed = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
William Entriken
fuente
1
esa es una idea terrible. Una URL tiene un límite de longitud que puede excederse fácilmente si está serializando JSON en ella.
Michael Baldry
Son alrededor de 2000 caracteres
Michael Baldry