How can I build a URL with query parameters containing multiple values for the same key in Swift?
Solution 1:
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
Solution 2:
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")
Solution 3:
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
}
Solution 4:
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 ofself
), 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