0

I am trying to create URL from String like the following

let searchParam = "?name=movies&Genre=#Action"

func searchMovies(searchString: String) {
    let encodedString = searchParam.encodeSearchString()
    let urlString = "https://search.movies.local/list.html" + encodedString

    guard let url = URL(string: searchParam) else {
        return
    }

    print("URL: ", url)
}

func encodeSearchString() -> String? {
  let unreserved = "#?=&"
  let allowed = NSMutableCharacterSet.alphanumeric()
  allowed.addCharacters(in: unreserved)
  return addingPercentEncoding(withAllowedCharacters: allowed as CharacterSet)
}

It works fine when the search param is "?name=movies&Genre=#Action" but if the search param contains more than one #, then the URL is nil.

For eg., if the searchParam is "?name=#movies&Genre=#Action"

2
  • Use URLQueryItems instead: var urlComponents = URLComponents(string: urlString); urlComponents?.queryItems = [URLQueryItem(name: "name", value: "movies"), URLQueryItem(name: "Genre", value: "#Action")]; guard let url = urlComponents?.url else { return } Commented Jan 15, 2021 at 17:19
  • @Larme, Thank you. I wanted to pass the query param as Custom String Commented Jan 15, 2021 at 17:25

1 Answer 1

2

The problem is that the # character is the separator for the fragment of the URL.

The most reliable way to build an URL with multiple components is URLComponents and URLQueryItem, the encoding is free

func searchMovies(with parameters: [String:String]) {
    var urlComponents = URLComponents(string: "https://search.movies.local/list.html")!
    var queryItems = [URLQueryItem]()
    for (key, value) in parameters {
        queryItems.append(URLQueryItem(name: key, value: value))
    }
    if !queryItems.isEmpty { urlComponents.queryItems = queryItems }
    print("URL: ", urlComponents.url) // https://search.movies.local/list.html?genre=%23action&name=%23movie
}


searchMovies(with: ["name":"#movie","genre":"#action"])

Or

func searchMovies(by query: String?) {
    var urlComponents = URLComponents(string: "https://search.movies.local/list.html")!
    var queryItems = [URLQueryItem]()
    if let queryString = query {
        let queryPairs = queryString.components(separatedBy: "&")
        for aPair in queryPairs {
            let keyValue = aPair.components(separatedBy: "=")
            if keyValue.count != 2 { continue }
            queryItems.append(URLQueryItem(name: keyValue[0], value: keyValue[1]))
        }
        if !queryItems.isEmpty { urlComponents.queryItems = queryItems }
    }
    print("URL: ", urlComponents.url) // https://search.movies.local/list.html?genre=%23action&name=%23movie
}

searchMovies(by: "name=#movies&Genre=#Action")
Sign up to request clarification or add additional context in comments.

2 Comments

urlComponents.queryItems = queryItems => urlComponents.queryItems = queryItems.isEmpty ? nil : queryItems else, it will add a ? at the end of the URL which might usually not be the desired output.
Good answer. Needless to say, the first approach is preferable, because the former technique will fail if the manually built string had a & in it (e.g. searching for a title like "Bonnie & Clyde").

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.