Working with URL’s is such a frequent task, but very tedious when it comes to parsing and manipulating it. Although there are tons of libraries out there dedicated to alleviating URL string woes, it is rare to find anything good natively built into the language. Objective-C and Swift were slow to the scene, but alas Apple has introduced NSURLComponents starting in iOS7. Just in time for implementing Universal Links 😉
Query String Parameters
Adding, updating, and removing query string parameters is a common use case. It’s a common piece in my coding arsenal across languages. Now it is time to include one for Swift, here’s the code:
/**
Add, update, or remove a query string item from the URL
:param: url the URL
:param: key the key of the query string item
:param: value the value to replace the query string item, nil will remove item
:returns: the URL with the mutated query string
*/
public func addOrUpdateQueryStringParameter(url: String, key: String, value: String?) -> String {
if let components = NSURLComponents(string: url),
var queryItems = (components.queryItems ?? []) as? [NSURLQueryItem] {
for (index, item) in enumerate(queryItems) {
// Match query string key and update
if item.name == key {
if let v = value {
queryItems[index] = NSURLQueryItem(name: key, value: v)
} else {
queryItems.removeAtIndex(index)
}
components.queryItems = queryItems.count > 0
? queryItems : nil
return components.string!
}
}
// Key doesn't exist if reaches here
if let v = value {
// Add key to URL query string
queryItems.append(NSURLQueryItem(name: key, value: v))
components.queryItems = queryItems
return components.string!
}
}
return url
}
/**
Add, update, or remove a query string parameters from the URL
:param: url the URL
:param: values the dictionary of query string parameters to replace
:returns: the URL with the mutated query string
*/
public func addOrUpdateQueryStringParameter(url: String, values: [String: String]) -> String {
var newUrl = url
for item in values {
newUrl = addOrUpdateQueryStringParameter(newUrl, key: item.0, value: item.1)
}
return newUrl
}
/**
Removes a query string item from the URL
:param: url the URL
:param: key the key of the query string item
:returns: the URL with the mutated query string
*/
public func removeQueryStringParameter(url: String, key: String) -> String {
return addOrUpdateQueryStringParameter(url, key: key, value: nil)
}
First and foremost, I’m splitting up the URL into pieces using `NSURLComponents`. This will allow me to work with just the query string parameters leaving everything else intact.
Next, I’m looping through the query items of the URL components and finding the key. If found, I am updating the query string parameter, or removing it if the value is `nil`. The reason why I am checking for `nil` is so I can reuse the function in the `removeQueryString` function by simply passing `nil`.
Finally, I’m assigning the mutated query items value back into the components and returning the components as a URL string. If there are no query items, I’m assigning `nil` so I don’t get an empty `?` appended to the end of the URL.
Notice if the loop doesn’t return anything, it means that no query string parameters was found, so I am creating a new query items array and assigning it back. It also gets there if the query items was nil or empty to begin with.
And as an added bonus, I’ve included a function to accept a dictionary of query string parameters to do a bulk update in one pass.
**UPDATE**
Refactored code and will be maintained here along with unit tests.
import Foundation.NSURL
public extension URL {
/// Returns a URL constructed by appending the given query string parameter to self.
///
/// let url = URL(string: "https://example.com?abc=123&lmn=tuv&xyz=987")
/// url?.appendingQueryItem("def", value: "456") // "https://example.com?abc=123&lmn=tuv&xyz=987&def=456"
/// url?.appendingQueryItem("xyz", value: "999") // "https://example.com?abc=123&lmn=tuv&xyz=999"
///
/// - Parameters:
/// - name: The key of the query string parameter.
/// - value: The value to replace the query string parameter, nil will remove item.
/// - Returns: The URL with the appended query string.
func appendingQueryItem(_ name: String, value: Any?) -> URL {
guard var urlComponents = URLComponents(string: absoluteString) else {
return self
}
urlComponents.queryItems = urlComponents.queryItems?
.filter { $0.name.caseInsensitiveCompare(name) != .orderedSame } ?? []
// Skip if nil value
if let value = value {
urlComponents.queryItems?.append(URLQueryItem(name: name, value: "\(value)"))
}
return urlComponents.url ?? self
}
/// Returns a URL constructed by appending the given query string parameters to self.
///
/// let url = URL(string: "https://example.com?abc=123&lmn=tuv&xyz=987")
/// url?.appendingQueryItems([
/// "def": "456",
/// "jkl": "777",
/// "abc": "333",
/// "lmn": nil
/// ]) // "https://example.com?xyz=987&def=456&abc=333&jkl=777"
///
/// - Parameter contentsOf: A dictionary of query string parameters to modify.
/// - Returns: The URL with the appended query string.
func appendingQueryItems(_ contentsOf: [String: Any?]) -> URL {
guard var urlComponents = URLComponents(string: absoluteString), !contentsOf.isEmpty else {
return self
}
let keys = contentsOf.keys.map { $0.lowercased() }
urlComponents.queryItems = urlComponents.queryItems?
.filter { !keys.contains($0.name.lowercased()) } ?? []
urlComponents.queryItems?.append(contentsOf: contentsOf.compactMap {
guard let value = $0.value else { return nil } //Skip if nil
return URLQueryItem(name: $0.key, value: "\(value)")
})
return urlComponents.url ?? self
}
/// Returns a URL constructed by removing the given query string parameter to self.
///
/// let url = URL(string: "https://example.com?abc=123&lmn=tuv&xyz=987")
/// url?.removeQueryItem("xyz") // "https://example.com?abc=123&lmn=tuv"
///
/// - Parameter name: The key of the query string parameter.
/// - Returns: The URL with the mutated query string.
func removeQueryItem(_ name: String) -> URL {
appendingQueryItem(name, value: nil)
}
}
public extension URL {
/// Query a URL from a parameter name.
///
/// let url = URL(string: "https://example.com?abc=123&lmn=tuv&xyz=987")
/// url?.queryItem("aBc") // "123"
/// url?.queryItem("lmn") // "tuv"
/// url?.queryItem("yyy") // nil
///
/// - Parameter name: The key of the query string parameter.
/// - Returns: The value of the query string parameter.
func queryItem(_ name: String) -> String? {
// https://stackoverflow.com/q/41421686
URLComponents(string: absoluteString)?
.queryItems?
.first { $0.name.caseInsensitiveCompare(name) == .orderedSame }?
.value
}
}
Enjoy!
HAPPY CODING!!
//Swift 2+ syntax
for (index, item) in queryItems.enumerate() {
god bless you 😀