155

This is my URL.

The problem is, that the address field is not being appended to urlpath.

Does anyone know why that is?

var address:string
address = "American Tourister, Abids Road, Bogulkunta, Hyderabad, Andhra Pradesh, India"
let urlpath = NSString(format: "http://maps.googleapis.com/maps/api/geocode/json?address="+"\(address)")
Demitrian
  • 3,200
  • 1
  • 24
  • 41
user3541467
  • 1,693
  • 3
  • 12
  • 6

13 Answers13

261

Swift 4.2

var urlString = originalString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)

Swift 3.0

var address = "American Tourister, Abids Road, Bogulkunta, Hyderabad, Andhra Pradesh, India"
let escapedAddress = address.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
let urlpath = String(format: "http://maps.googleapis.com/maps/api/geocode/json?address=\(escapedAddress)")

Use stringByAddingPercentEncodingWithAllowedCharacters:

var escapedAddress = address.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())

Use stringByAddingPercentEscapesUsingEncoding: Deprecated in iOS 9 and OS X v10.11

var address = "American Tourister, Abids Road, Bogulkunta, Hyderabad, Andhra Pradesh, India"
var escapedAddress = address.stringByAddingPercentEscapesUsingEncoding(NSUTF8StringEncoding)
let urlpath = NSString(format: "http://maps.googleapis.com/maps/api/geocode/json?address=\(escapedAddress)")
Ilesh P
  • 3,940
  • 1
  • 24
  • 49
Bryan Chen
  • 45,816
  • 18
  • 112
  • 143
  • 13
    You might want to modify that character set so that if `+` or `&` appear in the `address` value, that they get percent escaped. Unfortunately, `URLQueryAllowedCharacterSet` will let those pass unescaped, which will alter the value parsed by the server. – Rob Jul 22 '14 at 14:03
  • 8
    Rob is right, `var escapedAddress = address.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) ` is still blind to **&** and **+** character – pasevin Dec 17 '14 at 19:42
  • 5
    stringByAddingPercentEscapesUsingEncoding: is deprecated on iOS 9, better use stringByAddingPercentEncodingWithAllowedCharacters – webo80 Sep 24 '15 at 11:07
  • How do you modify the character set to include + and &? Why in the world does it not include them? – Jeremy Hicks Mar 01 '16 at 21:04
  • 5
    `let s = NSCharacterSet.URLQueryAllowedCharacterSet().mutableCopy() s.addCharactersInString("+&")` – Bryan Chen Mar 01 '16 at 21:11
  • let unreserved = "-._~/?" let allowed = NSMutableCharacterSet.alphanumericCharacterSet() allowed.addCharactersInString(unreserved) you can read more from http://useyourloaf.com/blog/how-to-percent-encode-a-url-string/ – gonglong Mar 22 '16 at 11:40
  • 1
    It only works for me if I remove them from the NSCharacterSet: let s = NSCharacterSet.URLQueryAllowedCharacterSet().mutableCopy() as! NSMutableCharacterSet; s.removeCharactersInString("+&"); let sSafe : String! = s.stringByAddingPercentEncodingWithAllowedCharacters(s) – geis Mar 23 '16 at 23:15
  • It worked perfectly for me using NSCharacterSet.URLHostAllowedCharacterSet() – agfa555 Mar 31 '16 at 10:07
  • 1
    @BryanChen - I think you meant `removeCharactersInString`, not `addCharactersInString`. – Rob Apr 05 '16 at 18:26
  • @gonglong - You don't want to use `alphanumericCharacterSet` because that's actually a huge character set that includes lots of international characters, which I'm sure you didn't intend. – Rob Apr 05 '16 at 18:27
  • 1
    @JeremyHicks - You asked why `URLQueryAllowedCharacterSet` allows `&` and `+` to pass unescaped. It does that because, technically, those characters _are_ allowed to be in the "query" portion of the URI, but it's just that they serve special purposes within `x-www-form-urlencoded` requests. So, my beef with Apple is not necessarily that they included them within `URLQueryAllowedCharacterSet` (because, within a strict reading of the RFCs, Apple is correct), but that they don't have a desperately needed character set that we can use for keys and values _within_ the `x-www-form-urlencoded` query. – Rob Apr 06 '16 at 00:42
  • 2
    urlString = urlString.addingPercentEncoding(withAllowedCharacters: NSCharacterSet.urlQueryAllowed)! – Abdul Yasin Nov 23 '16 at 06:22
84

If it's possible that the value that you're adding to your URL can have reserved characters (as defined by section 2 of RFC 3986), you might have to refine your percent-escaping. Notably, while & and + are valid characters in a URL, they're not valid within a URL query parameter value (because & is used as delimiter between query parameters which would prematurely terminate your value, and + is translated to a space character). Unfortunately, the standard percent-escaping leaves those delimiters unescaped.

Thus, you might want to percent escape all characters that are not within RFC 3986's list of unreserved characters:

Characters that are allowed in a URI but do not have a reserved purpose are called unreserved. These include uppercase and lowercase letters, decimal digits, hyphen, period, underscore, and tilde.

     unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"

Later, in section 3.4, the RFC further contemplates adding ? and / to the list of allowed characters within a query:

The characters slash ("/") and question mark ("?") may represent data within the query component. Beware that some older, erroneous implementations may not handle such data correctly when it is used as the base URI for relative references (Section 5.1), apparently because they fail to distinguish query data from path data when looking for hierarchical separators. However, as query components are often used to carry identifying information in the form of "key=value" pairs and one frequently used value is a reference to another URI, it is sometimes better for usability to avoid percent- encoding those characters.

Nowadays, you'd generally use URLComponents to percent escape the query value:

var address = "American Tourister, Abids Road, Bogulkunta, Hyderabad, Andhra Pradesh, India"
var components = URLComponents(string: "http://maps.googleapis.com/maps/api/geocode/json")!
components.queryItems = [URLQueryItem(name: "address", value: address)]
let url = components.url!

By the way, while it's not contemplated in the aforementioned RFC, section 5.2, URL-encoded form data, of the W3C HTML spec says that application/x-www-form-urlencoded requests should also replace space characters with + characters (and includes the asterisk in the characters that should not be escaped). And, unfortunately, URLComponents won't properly percent escape this, so Apple advises that you manually percent escape it before retrieving the url property of the URLComponents object:

// configure `components` as shown above, and then:

components.percentEncodedQuery = components.percentEncodedQuery?.replacingOccurrences(of: "+", with: "%2B")
let url = components.url!

For Swift 2 rendition, where I manually do all of this percent escaping myself, see the previous revision of this answer.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • 9
    This is more advanced and bulletproof response. – pasevin Dec 17 '14 at 19:50
  • Are you missing a ~ in the last example? e.g. characterSet.addCharactersInString("-._~* ") – Nick Snyder Mar 03 '15 at 23:18
  • 1
    No, that's my point. RFC3986 and the W3C HTML spec are not entirely consistent. The W3C HTML spec explicitly says "If the byte is in the range 0x2A (`*`), 0x2D (`-`), 0x2E (`.`), 0x30 to 0x39 (`0`-`9`), 0x41 to 0x5A (`A`-`Z`), 0x5F (`_`), 0x61 to 0x7A (`a`-`z`), leave the byte as is, [otherwise percent escape]." The W3C spec doesn't contemplate `~`. In practice, it probably doesn't really matter that much (most web servers will accept it unescaped). – Rob Mar 03 '15 at 23:39
  • 3
    +1 for the x-www-form-urlencoded part. Every other answer I've seen out there fails to work for that situation. – Chadwick Aug 11 '15 at 14:19
  • This is exactly what I need for sending + in the query variable. Thanks for the documentation as well. – Keith Holliday Jan 13 '16 at 17:03
  • There're 103806 characters in the `alphanumericCharacterSet` (almost entire Unicode), this might be too slow. See my answer for a more lightweight method. – Desmond Hume Jan 14 '16 at 11:00
  • You are correct that we really should use a character set that is more strictly in conformance with the RFC. I've revised my answer accordingly. – Rob Jan 16 '16 at 17:51
  • With the last example, I believe `+` is included in `URLQueryParameterAllowedCharacterSet`, so you are not encoding the `+` characters (before converting the spaces to `+`s). You need to add a line: `allowedCharacters.removeCharactersInString("+")` which will encode the actual instances of `+`. – Daniel Tull May 31 '16 at 13:04
  • @DanielTull - No, I suspect you are confusing my custom `URLQueryParameterAllowedCharacterSet` (which does not include `+`) with the system method, `URLQueryAllowedCharacterSet` (which does). – Rob May 31 '16 at 13:47
66

Swift 3:

let escapedString = originalString.addingPercentEncoding(withAllowedCharacters:NSCharacterSet.urlQueryAllowed)
Yusuf X
  • 14,513
  • 5
  • 35
  • 47
35

Swift 2.0

let needsLove = "string needin some URL love"
let safeURL = needsLove.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!

Helpful Progamming Tips and Hacks

quemeful
  • 9,542
  • 4
  • 60
  • 69
16

URLQueryAllowedCharacterSet should not be used for URL encoding of query parameters because this charset includes &, ?, / etc. which serve as delimiters in a URL query, e.g.

/?paramname=paramvalue&paramname=paramvalue

These characters are allowed in URL queries as a whole but not in parameter values.

RFC 3986 specifically talks about unreserved characters, which are different from allowed:

2.3. Unreserved Characters

Characters that are allowed in a URI but do not have a reserved
purpose are called unreserved. These include uppercase and lowercase letters, decimal digits, hyphen, period, underscore, and tilde.

  unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"

Accordingly:

extension String {
    var URLEncoded:String {

        let unreservedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
        let unreservedCharsSet: CharacterSet = CharacterSet(charactersIn: unreservedChars)
        let encodedString = self.addingPercentEncoding(withAllowedCharacters: unreservedCharsSet)!
        return encodedString
    }
}

The code above doesn't make a call to alphanumericCharacterSet because of the enormous size of the charset it returns (103806 characters). And in view of how many Unicode characters alphanumericCharacterSet allows for, using it for the purpose of URL encoding would be simply erroneous.

Usage:

let URLEncodedString = myString.URLEncoded
Mahendra Liya
  • 12,912
  • 14
  • 88
  • 114
Desmond Hume
  • 8,037
  • 14
  • 65
  • 112
  • 1
    Nice answer. But you posted it twice, it would have been better to flag one of the questions as duplicate. – Eric Aya Jan 14 '16 at 11:49
  • 1
    Did I say otherwise? I even said "nice answer". :/ What I'm saying is *don't post the same answer multiple times*, that's all. – Eric Aya Jan 14 '16 at 12:10
  • There are so many wrong answers around this topic that I felt that more people should see this answer before they start adding bugs into their code. But ok, gonna stick to this question exclusively now. – Desmond Hume Jan 14 '16 at 12:27
  • This is not true, you should use `URLQueryAllowedCharacterSet` for encoding you queries. But not you totally query! You should encode each value of the total query with `URLQueryAllowedCharacterSet` so it should work. – rckoenes Mar 08 '18 at 14:17
8

Swift 4.1

Create a "Character Set" based on the option you want (urlQueryAllowed). Then remove the additional characters you do not want (+&). Then pass that character set to "addingPercentEncoding".

var address = "American Tourister, Abids Road, Bogulkunta, Hyderabad, Andhra Pradesh, India"
var queryCharSet = NSCharacterSet.urlQueryAllowed
queryCharSet.remove(charactersIn: "+&")
let escapedAddress = address.addingPercentEncoding(withAllowedCharacters: queryCharSet)!
let urlpath = String(format: "http://maps.googleapis.com/maps/api/geocode/json?address=\(escapedAddress)")
Daryl
  • 772
  • 8
  • 21
  • This is what I ended up doing too. I'd think they need a urlQueryValueAllowed too (though I supposed technically + is *allowed* as part of a query value too). – Dylan Nicholson May 30 '18 at 02:08
6

XCODE 8, SWIFT 3.0

From grokswift

Creating URLs from strings is a minefield for bugs. Just miss a single / or accidentally URL encode the ? in a query and your API call will fail and your app won’t have any data to display (or even crash if you didn’t anticipate that possibility). Since iOS 8 there’s a better way to build URLs using NSURLComponents and NSURLQueryItems.

func createURLWithComponents() -> URL? {
    var urlComponents = URLComponents()
    urlComponents.scheme = "http"
    urlComponents.host = "maps.googleapis.com"
    urlComponents.path = "/maps/api/geocode/json"

    let addressQuery = URLQueryItem(name: "address", value: "American Tourister, Abids Road, Bogulkunta, Hyderabad, Andhra Pradesh, India")
    urlComponents.queryItems = [addressQuery]

    return urlComponents.url
}

Below is the code to access url using guard statement.

guard let url = createURLWithComponents() else {
            print("invalid URL")
            return nil
      }
      print(url)

Output:

http://maps.googleapis.com/maps/api/geocode/json?address=American%20Tourister,%20Abids%20Road,%20Bogulkunta,%20Hyderabad,%20Andhra%20Pradesh,%20India

Read More: Building URLs With NSURLComponents and NSURLQueryItems

Ashok R
  • 19,892
  • 8
  • 68
  • 68
5

Just completing Desmond Hume's answer to extend the String class for a RFC 3986 unreserved characters valid encoding function (needed if you are encoding query FORM parameters):

Swift 3

extension String {

    var RFC3986UnreservedEncoded:String {
        let unreservedChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"
        let unreservedCharsSet: CharacterSet = CharacterSet(charactersIn: unreservedChars)
        let encodedString: String = self.addingPercentEncoding(withAllowedCharacters: unreservedCharsSet)!
        return encodedString
    }
}
Jorge Ramírez
  • 657
  • 1
  • 7
  • 8
4

Updated for Swift 3:

var escapedAddress = address.addingPercentEncoding(
    withAllowedCharacters: CharacterSet.urlQueryAllowed)
Pavlos
  • 906
  • 1
  • 11
  • 29
3

In Mac OS 10.9 Maverics and iOS 7 NSURLComponents has been introduced which handles the encoding of the different URL parts in a pretty convenient way.

The NSURLComponents class is a class that is designed to parse URLs based on RFC 3986 and to construct URLs from their constituent parts. Its behavior differs subtly from the NSURL class, which conforms to older RFCs. However, you can easily obtain an NSURL object based on the contents of a URL components object or vice versa.

let address = "American Tourister, Abids Road, Bogulkunta, Hyderabad, Andhra Pradesh, India"

let components = NSURLComponents(string: "http://maps.googleapis.com/maps/api/geocode/json")!
// create a query item key=value
let queryItem = NSURLQueryItem(name: "address", value: address)
// add the query item to the URL, NSURLComponents takes care of adding the question mark.
components.queryItems = [queryItem]
// get the properly percent encoded string
let urlpath = components.string!
print(urlpath)
vadian
  • 274,689
  • 30
  • 353
  • 361
0

Adding to Bryan Chen's answer above:

Just incase anyone else is getting something similar with Alamofire:

error: Alamofire was compiled with optimization - stepping may behave oddly; variables may not be available.

It's not a very descriptive error. I was getting that error when constructing a URL for google geo services. I was appending a street address to the end of the URL WITHOUT encoding the street address itself first. I was able to fix it using Bryan Chen's solution:

var streetAdress = "123 fake street, new york, ny"
var escapedStreetAddress = streetAddress.stringByAddingPercentEncodingWithAllowedCharacters(.URLHostAllowedCharacterSet())
let url = "\(self.baseUrl)&address=\(escapedAddress!)"

That fixed it for me! It didnt like that the address had spaces and commas, etc.

Hope this helps someone else!

Geoherna
  • 3,523
  • 1
  • 24
  • 39
0

I needed to encode my parameters with ISO-8859-1, so the addingPercentEncoding() method doesn't work for me. I made a solution my self in Swift 4:

extension String {

  // Url percent encoding according to RFC3986 specifications
  // https://tools.ietf.org/html/rfc3986#section-2.1
  func urlPercentEncoded(withAllowedCharacters allowedCharacters: 
    CharacterSet, encoding: String.Encoding) -> String {
    var returnStr = ""

    // Compute each char seperatly
    for char in self {
      let charStr = String(char)
      let charScalar = charStr.unicodeScalars[charStr.unicodeScalars.startIndex]
      if allowedCharacters.contains(charScalar) == false,
        let bytesOfChar = charStr.data(using: encoding) {
        // Get the hexStr of every notAllowed-char-byte and put a % infront of it, append the result to the returnString
        for byte in bytesOfChar {
          returnStr += "%" + String(format: "%02hhX", byte as CVarArg)
        }
      } else {
        returnStr += charStr
      }
    }

    return returnStr
  }

}

Usage:

"aouäöü!".urlPercentEncoded(withAllowedCharacters: .urlQueryAllowed,
                                         encoding: .isoLatin1) 
// Results in -> "aou%E4%F6%FC!"
2h4u
  • 504
  • 3
  • 8
-1

In my case where the last component was non latin characters I did the following in Swift 2.2:

extension String {
 func encodeUTF8() -> String? {
//If I can create an NSURL out of the string nothing is wrong with it
if let _ = NSURL(string: self) {

    return self
}

//Get the last component from the string this will return subSequence
let optionalLastComponent = self.characters.split { $0 == "/" }.last


if let lastComponent = optionalLastComponent {

    //Get the string from the sub sequence by mapping the characters to [String] then reduce the array to String
    let lastComponentAsString = lastComponent.map { String($0) }.reduce("", combine: +)


    //Get the range of the last component
    if let rangeOfLastComponent = self.rangeOfString(lastComponentAsString) {
        //Get the string without its last component
        let stringWithoutLastComponent = self.substringToIndex(rangeOfLastComponent.startIndex)


        //Encode the last component
        if let lastComponentEncoded = lastComponentAsString.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.alphanumericCharacterSet()) {


        //Finally append the original string (without its last component) to the encoded part (encoded last component)
        let encodedString = stringWithoutLastComponent + lastComponentEncoded

            //Return the string (original string/encoded string)
            return encodedString
        }
    }
}

return nil;
}
}
Bobj-C
  • 5,276
  • 9
  • 47
  • 83