Pass Parameter in WebService in Swift

When creating a HTTP request that includes user input, one should generally percent escape it in case there are any reserved characters in the user’s input, thus:

let login    = logintextfield.text?.addingPercentEncodingForURLQueryValue() ?? ""
let password = passwordtextfield.text?.addingPercentEncodingForURLQueryValue() ?? ""
let bodyData = "email=\(login)&password=\(password)"

Note, you’d really want to check to see if login and password were nil or not. Anyway, the percent-escaping is done as follows:

extension String {

    /// Percent escapes values to be added to a URL query as specified in RFC 3986
    ///
    /// This percent-escapes all characters besides the alphanumeric character set and "-", ".", "_", and "~".
    ///
    /// http://www.ietf.org/rfc/rfc3986.txt
    ///
    /// :returns: Returns percent-escaped string.

    func addingPercentEncodingForURLQueryValue() -> String? {
        let allowedCharacters = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._~")

        return self.addingPercentEncoding(withAllowedCharacters: allowedCharacters)
    }

}

See this answer for another rendition of this extension.


If you wanted to see a demonstration of the use of the above, imagine the following request:

let keyData = "AIzaSyCRLa4LQZWNQBcjCYcIVYA45i9i8zfClqc"
let sensorInformation = false
let types = "building"
let radius = 1000000
let locationCoordinate = CLLocationCoordinate2D(latitude:40.748716, longitude: -73.985643)
let name = "Empire State Building, New York, NY"
let floors = 102
let now = Date()

let params:[String: Any] = [
    "key" : keyData,
    "sensor" : sensorInformation,
    "typesData" : types,
    "radius" : radius,
    "location" : locationCoordinate,
    "name" : name,
    "floors" : floors,
    "when" : now,
    "pi" : M_PI]

let url = URL(string: "http://some.web.site.com/inquiry")!
var request = URLRequest(url: url)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpBody = params.dataFromHttpParameters()

let task = URLSession.shared.dataTask(with: request) { data, response, error in
    guard data != nil && error == nil else {
        print("error submitting request: \(error)")
        return
    }

    if let httpResponse = response as? HTTPURLResponse where httpResponse.statusCode != 200 {
        print("response was not 200: \(response)")
        return
    }

    // handle the data of the successful response here
}
task.resume()

I’m including lots of parameters that were not included in your example, but simply as a way of illustrating the routine’s handling of a diverse array of parameter types.

Incidentally, the above uses my datafromHttpParameters function:

extension Dictionary {

    /// This creates a String representation of the supplied value.
    ///
    /// This converts NSDate objects to a RFC3339 formatted string, booleans to "true" or "false",
    /// and otherwise returns the default string representation.
    ///
    /// - parameter value: The value to be converted to a string
    ///
    /// - returns:         String representation

    private func httpStringRepresentation(_ value: Any) -> String {
        switch value {
        case let date as Date:
            return date.rfc3339String()
        case let coordinate as CLLocationCoordinate2D:
            return "\(coordinate.latitude),\(coordinate.longitude)"
        case let boolean as Bool:
            return boolean ? "true" : "false"
        default:
            return "\(value)"
        }
    }

    /// Build `Data` representation of HTTP parameter dictionary of keys and objects
    ///
    /// This percent escapes in compliance with RFC 3986
    ///
    /// http://www.ietf.org/rfc/rfc3986.txt
    ///
    /// :returns: String representation in the form of key1=value1&key2=value2 where the keys and values are percent escaped

    func dataFromHttpParameters() -> Data {
        let parameterArray = self.map { (key, value) -> String in
            let percentEscapedKey = (key as! String).addingPercentEncodingForURLQueryValue()!
            let percentEscapedValue = httpStringRepresentation(value).addingPercentEncodingForURLQueryValue()!
            return "\(percentEscapedKey)=\(percentEscapedValue)"
        }

        return parameterArray.joined(separator: "&").data(using: .utf8)!
    }

}

Here, because I’m dealing with an array of parameter strings, I use the join function to concatenate them separated by &, but the idea the same.

Feel free to customize that function to handle whatever data types you may be passing into it (e.g. I don’t generally have CLLocationCoordinate2D in there, but your example included one, so I wanted to show what it might look like). But the key is that if you’re supplying any fields that include user input, make sure to percent-escape it.

FYI, this is my rfc3339String function which is used above. (Clearly, if you don’t need to transmit dates, you don’t need this, but I’m including it for the sake of completeness for a more generalized solution.)

extension Date {

    /// Get RFC 3339/ISO 8601 string representation of the date.
    ///
    /// For more information, see:
    ///
    /// https://developer.apple.com/library/ios/qa/qa1480/_index.html
    ///
    /// - returns: Return RFC 3339 representation of date string

    func rfc3339String() -> String {
        let formatter = DateFormatter()

        formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSX"
        formatter.timeZone = TimeZone(secondsFromGMT: 0)
        formatter.locale = Locale(identifier: "en_US_POSIX")

        return formatter.string(from: self)
    }
}

To see Swift 2 rendition, see previous rendition of this answer.

Leave a Comment