URLComponents is behaving correctly: the +
is not being percent-encoded because it is legal as it stands. You can force the +
to be percent-encoded by using .alphanumerics
, as explained already by Forest Kunecke (I got the same result independently but he was well ahead of me in submitting his answer!).
Just a couple of refinements. The OP's value: "\($1)"
is unnecessary if this is a string; you can just say value:$1
. And, it would be better to form the URL from all its components.
This, therefore, is essentially the same solution as Forest Kunecke, but I think it is more canonical and it is certainly more compact ultimately:
let queryParams = ["hey":"ho+ha"]
var components = URLComponents()
components.scheme = "http"
components.host = "www.example.com"
components.path = "/somepath"
components.queryItems = queryParams.map {
URLQueryItem(name: $0,
value: $1.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!)
}
let finalURL = components.url
EDIT Rather better, perhaps, after suggested correction from Martin R: we form the entire query and percent-encode the pieces ourselves, and tell the URLComponents that we have done so:
let queryParams = ["hey":"ho+ha", "yo":"de,ho"]
var components = URLComponents()
components.scheme = "http"
components.host = "www.example.com"
components.path = "/somepath"
var cs = CharacterSet.urlQueryAllowed
cs.remove("+")
components.percentEncodedQuery = queryParams.map {
$0.addingPercentEncoding(withAllowedCharacters: cs)! +
"=" +
$1.addingPercentEncoding(withAllowedCharacters: cs)!
}.joined(separator:"&")
// ---- Okay, let's see what we've got ----
components.queryItems
// [{name "hey", {some "ho+ha"}}, {name "yo", {some "de,ho"}}]
components.url
// http://www.example.com/somepath?hey=ho%2Bha&yo=de,ho