88

I am using AFNetworking in my iOS app and for all the GET requests it makes, I build the url from a base URL and than add parameters using NSDictionary Key-Value pairs.

The problem is that I need same key for different values.

Here is an example of what I need the finally URL to look like -

http://example.com/.....&id=21212&id=21212&id=33232

It's not possible in NSDictionary to have different values in same keys. So I tried NSSet but did not work.

let productIDSet: Set = [prodIDArray]
let paramDict = NSMutableDictionary()
paramDict.setObject(productIDSet, forKey: "id")
Cœur
  • 37,241
  • 25
  • 195
  • 267
AbhishekDwivedi
  • 883
  • 1
  • 7
  • 5

8 Answers8

182

All you need is URLComponents (or NSURLComponents in Obj-C). The basic idea is to create a bunch of query items for your id's. Here's code you can paste into a playground:

import Foundation
import XCPlayground

let queryItems = [URLQueryItem(name: "id", value: "1"), URLQueryItem(name: "id", value: "2")]
var urlComps = URLComponents(string: "www.apple.com/help")!
urlComps.queryItems = queryItems
let result = urlComps.url!
print(result)

You should see an output of

www.apple.com/help?id=1&id=2

Daniel Galasko
  • 23,617
  • 8
  • 77
  • 97
  • Awesome. Thanks for your answer. I tried looping through and add the value in a for loop as I have my IDs in an array but I am facing a lot of issues. Will you be able to please suggest how do I add values in loop ? Many thanks. – AbhishekDwivedi Dec 03 '15 at 09:08
  • sure, just make sure you declare your array of `queryItems` as a `var` and then you can add new items in a loop @AbhishekDwivedi – Daniel Galasko Dec 03 '15 at 09:24
  • Sorry @Daniel to bug you but this is what I am trying to do but not able to. Not sure what the problem is :( var queryItems = [NSURLQueryItem]() let urlComps = NSURLComponents(string: "www.apple.com/help")! for var i = 0 ; i < prodIDArray.count ; i++ { let queryitem = [NSURLQueryItem(name: "id", value: prodIDArray[i] as? String)] queryItems.append(queryitem) } urlComps.queryItems = queryitems – AbhishekDwivedi Dec 03 '15 at 09:59
  • You don't need to create a query item array in the loop. Just remove the square brackets and you should be good @AbhishekDwivedi – Daniel Galasko Dec 03 '15 at 10:16
  • My url is like below https://www.abcd.com/json/trend/id/12/key/23456 How can i achieve this ? I am using alamofire 4.0 – Ravi Ojha Mar 10 '17 at 12:11
  • If anyone get this `Expression type '()' is ambiguous without more context` error, use URLQueryItem instead of NSURLQueryItem – Muhammad Yusuf Apr 08 '20 at 18:12
85

Method 1

It can add the QueryItem to your existing URL.

extension URL {

    func appending(_ queryItem: String, value: String?) -> URL {

        guard var urlComponents = URLComponents(string: absoluteString) else { return absoluteURL }

        // Create array of existing query items
        var queryItems: [URLQueryItem] = urlComponents.queryItems ??  []

        // Create query item
        let queryItem = URLQueryItem(name: queryItem, value: value)

        // Append the new query item in the existing query items array
        queryItems.append(queryItem)

        // Append updated query items array in the url component object
        urlComponents.queryItems = queryItems

        // Returns the url from new url components
        return urlComponents.url!
    }
}

How to use

var url = URL(string: "https://www.example.com")!
let finalURL = url.appending("test", value: "123")
                  .appending("test2", value: nil)

Method 2

In this method, the URL will be updated automatically.

extension URL {

    mutating func appendQueryItem(name: String, value: String?) {

        guard var urlComponents = URLComponents(string: absoluteString) else { return }

        // Create array of existing query items
        var queryItems: [URLQueryItem] = urlComponents.queryItems ??  []

        // Create query item
        let queryItem = URLQueryItem(name: name, value: value)

        // Append the new query item in the existing query items array
        queryItems.append(queryItem)

        // Append updated query items array in the url component object
        urlComponents.queryItems = queryItems

        // Returns the url from new url components
        self = urlComponents.url!
    }
}

// How to use
var url = URL(string: "https://www.example.com")!
url.appendQueryItem(name: "name", value: "bhuvan")
Bhuvan Bhatt
  • 3,276
  • 2
  • 18
  • 23
12
func queryString(_ value: String, params: [String: String]) -> String? {    
    var components = URLComponents(string: value)
    components?.queryItems = params.map { element in URLQueryItem(name: element.key, value: element.value) }

    return components?.url?.absoluteString
}
dimpiax
  • 12,093
  • 5
  • 62
  • 45
Kirit Vaghela
  • 12,572
  • 4
  • 76
  • 80
10

An URL extension to append query items, similar to Bhuvan Bhatt idea, but with a different signature:

  • it can detect failures (by returning nil instead of self), thus allowing custom handling of cases where the URL is not RFC 3986 compliant for instance.
  • it allows nil values, by actually passing any query items as parameters.
  • for performance, it allows passing multiple query items at a time.
extension URL {
    /// Returns a new URL by adding the query items, or nil if the URL doesn't support it.
    /// URL must conform to RFC 3986.
    func appending(_ queryItems: [URLQueryItem]) -> URL? {
        guard var urlComponents = URLComponents(url: self, resolvingAgainstBaseURL: true) else {
            // URL is not conforming to RFC 3986 (maybe it is only conforming to RFC 1808, RFC 1738, and RFC 2732)
            return nil
        }
        // append the query items to the existing ones
        urlComponents.queryItems = (urlComponents.queryItems ?? []) + queryItems

        // return the url from new url components
        return urlComponents.url
    }
}

Usage

let url = URL(string: "https://example.com/...")!
let queryItems = [URLQueryItem(name: "id", value: nil),
                  URLQueryItem(name: "id", value: "22"),
                  URLQueryItem(name: "id", value: "33")]
let newUrl = url.appending(queryItems)!
print(newUrl)

Output:

https://example.com/...?id&id=22&id=33

Cœur
  • 37,241
  • 25
  • 195
  • 267
  • I don't think it is a good idea to allow same key multiple times in a url – atulkhatri Apr 03 '20 at 08:26
  • @atulkhatri while the RFC 3986 **allows** it, I'm well aware that, for instance, [on Windows Phone 7/8, the Operating System won't even let you handle URLs with duplicate keys](https://stackoverflow.com/a/26341732/1033581). So I fully agree that it's not the best practice. Consequently its usage will depend on your API. This is [how I filter duplicate keys in Swift](https://stackoverflow.com/a/55684308/1033581): with the code from it, you could add `queryItems.unique(for: \.name)` if you want. – Cœur Apr 03 '20 at 11:46
  • I completely understand your intention but I feel being it a front-end code, the app does not know how the server is going to handle it. So in order to be on a safer side, I think it is best to not have such inconsistent query parameters. – atulkhatri Apr 04 '20 at 11:21
7

Update for iOS 16, Swift 5.7+

There's a shorter way available to perform this:

var url = URL(string: "http://google.com/search")
url?.append(queryItems: [URLQueryItem(name: "q", value: "soccer")])
print(url) // http://www.google.com/search?q=soccer
Amir.n3t
  • 2,859
  • 3
  • 21
  • 28
  • 1
    In iOS 16 there is even the non mutating function `appending`, which *returns* an appended URL. – Jan Mar 01 '23 at 16:58
4

2019

private func tellServerSomething(_ d: String, _ s: String) {
    
    var c = URLComponents(string: "https://you.com/info")
    c?.queryItems = [
        URLQueryItem(name: "description", value: d),
        URLQueryItem(name: "summary", value: s)
    ]
    guard let u = c?.url else { return print("url fail") }
    do {
        let r = try String(contentsOf: u)
        print("Server response \(r)")
    }
    catch { return print("comms fail") }
}

Percent-encoding and everything else is handled.

Community
  • 1
  • 1
Fattie
  • 27,874
  • 70
  • 431
  • 719
1

In Swift Forming URL with multiple params

func rateConversionURL(with array: [String]) -> URL? {
            var components = URLComponents()
            components.scheme = "https"
            components.host = "example.com"
            components.path = "/hello/"
            components.queryItems = array.map { URLQueryItem(name: "value", value: $0)}

        return components.url
    }
Wasim
  • 921
  • 12
  • 14
-4

I guess u just have to do something like this:

let params = ["id" : [1, 2, 3, 4], ...];

which will be encoded into: ....id%5B%5D=1&id%5B%5D=2&id%5B%5D=3&id%5B%5D=4....

Serg Dort
  • 477
  • 2
  • 5
  • And If you are working with swift I sagest you to use [Alamofire](https://github.com/Alamofire/Alamofire) – Serg Dort Dec 03 '15 at 08:25