Encoding UIImage to Base64 string not working when transferred to server - ios

Encoding an UIImage as a Base64 string works on the device, but transferring the string to the server somehow corrupts the string and prevents the server from successfully decoding the image.
Any suggestions on the problem?
// Define params
params["thumbnail_base64"] = imageToBase64(blockSet.thumbnailURL)
...
// Convert params -> query string
let postString = buildQueryString(params)
// Define upload URL
let uploadURL = NSURL(string: RootURL + UploadFilePath)!
// Hit server
let request = NSMutableURLRequest(URL: uploadURL)
request.HTTPMethod = "POST"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
...
private func buildQueryString(parameters: [String:String], omitQuestionMark: Bool = false) -> String {
var urlVars = [String]()
for (k, var v) in parameters {
v = v.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
urlVars += [k + "=" + "\(v)"]
}
return ((urlVars.isEmpty || omitQuestionMark) ? "" : "?") + urlVars.joinWithSeparator("&")
}
private func imageToBase64(filename: String) -> String {
// Get image path
let imagePath = getFilePath(filename)
// Convert image to base64 or return empty string
if let imageData = NSData(contentsOfFile: imagePath) {
let base64String = imageData.base64EncodedStringWithOptions(.EncodingEndLineWithLineFeed)
return base64String
} else {
printError("Error converting image to Base64: missing image. Filename: \(filename)")
return ""
}
}

The problem is with the queryString, base64 is long text with many characters, let JSON do the work for you
Use the next (with some example of NodeJS)
let params = NSMutableDictionary();
//you can only set `serializable` values
params.setValue(imageToBase64(),forKey:"base64")
params.setValue(username,forKey:"username")
params.setValue(["array","of","string"],forKey:"arr")
let uploadURL = NSURL(string: theURL)!
// Hit server
let request = NSMutableURLRequest(URL: uploadURL)
request.HTTPMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
do {
let jsonData = try NSJSONSerialization.dataWithJSONObject(params, options: NSJSONWritingOptions(rawValue: 0))
request.HTTPBody = jsonData
let session = NSURLSession.sharedSession()
session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
print("Response: \(response)")
}).resume()
} catch let error as NSError {
print(error)
}
nodejs:
var buffer = new Buffer(request.body["base64"], 'base64')
fs.writeFile('test.jpeg',buffer,"base64"); //Works
var username = request.body["username"];
var someStringsArr = request.body["arr"]
by the way...
you wrote the function buildQueryString, which is already exists in Foundation
let urlComponents = NSURLComponents(string: "http://myUrl.com/getApi/")!
urlComponents.queryItems = [NSURLQueryItem]()
urlComponents.queryItems!.append(NSURLQueryItem(name:"myKeyA",value:"myValueA"))
urlComponents.queryItems!.append(NSURLQueryItem(name:"myKeyB",value:"myValueB"))
print(urlComponents.URL!) //http://myUrl.com/getApi/?myKeyA=myValueA&myKeyB=myValueB
Use url query if want to send GET parameters via the URL

Related

Get request doesn't get executed in swift, getting nil value error where URL is passed in the request

I am trying to add a GET request in my application.
The values or the final url string is flowing correctly in my sURL variable. But still while executing this code I get "Found nil error" at line - "var request = URLRequest(url: URL(string: sUrl)!)"
Please help.
My code -
class AllStickerService {
static let allStickerInstance: AllStickerService = AllStickerService()
var delegateAllSticker: AllStickerProtocol!
func fetchAllSticker(category: String, APITokenString: String) {
var sUrl = "http://xyzabc.com/api/stickers"
let params = ["category": category]
var sParams = ""
for (key,value) in params {
sParams += key + "=" + value
print("\(key), \(value)")
}
if !sParams.isEmpty {
sParams = "?" + sParams
sUrl = sUrl + sParams
}
var request = URLRequest(url: URL(string: sUrl)!)
print(request)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("Bearer "+APITokenString, forHTTPHeaderField: "Authorization")
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if (response as? HTTPURLResponse) != nil {
if let httpResponse = response as? HTTPURLResponse {
print("statusCode: \(httpResponse.statusCode)")
print(httpResponse)
}
if let data = data{
do {
guard let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else { return }
print(json)
}catch {
print("Error\(error)")
}
}
}
}.resume()
}
}
Force unwrapping is rarely a good idea. Either is using string concatenation to create URLs; Aside from potential security problems, you have to worry about things like url encoding, which is your problem here.
Your category parameter value has a space, this needs to be encoded as %20, but you don't do this and you end up with an invalid URL string. You don't see this with Postman because it is encoding the space for you behind the scenes.
A better approach is to use URLComponents, URLQueryItem and use conditional unwrapping
func fetchAllSticker(category: String, APITokenString: String) {
var sUrl = "http://xyzabc.com/api/stickers"
let params = URLQueryItem(name:"category", value: category)
if var urlComponents = URLComponents(string:"http://xyzabc.com/api/stickers") {
urlComponents.queryItems = params
if let url = urlComponents.url {
var request = URLRequest(url: url)
...
}
}
I would also recommend you look into using Decodable to handle your JSON response rather than JSONSerialization

How to append Special character in iOS using swift?

I am new to iOS please consider.I want to post some data to server, but I am not able to send Mobile number +91 and bloodGroup A+..it was sending to firebase + is replace with space " " like this ( 91) and (A )
func addEmployees(){
let photoUrl = "https://firebasestorage.googleapis.com/v0/b/pickcel-1241.appspot.com/o/task.careGallery%2FGroup%2018aasa.png?alt=media&token=4e0ac8f6-134a-4807-9fef-f44eabe9f6a8";
let userID = Auth.auth().currentUser!.uid
let mobilenumber = Auth.auth().currentUser?.phoneNumber
var employeeDetails = [String : AnyObject]()
employeeDetails["OID"] = getOID() as AnyObject
employeeDetails["MID"] = userID as AnyObject
employeeDetails["email"] = "ssinth#gmail.com" as AnyObject
employeeDetails["firstName"] = "First Name" as AnyObject
employeeDetails["lastName"] = "last name" as AnyObject
employeeDetails["isManager"] = "true" as AnyObject
employeeDetails["regMedia"] = "mobile" as AnyObject
employeeDetails["shortDestination"] = "btm" as AnyObject
employeeDetails["address"] = "+btm" as AnyObject
employeeDetails["createdDate"] = getdateformat() as AnyObject
employeeDetails["orgName"] = "Test Org" as AnyObject
employeeDetails["photoUrl"] = photoUrl as AnyObject
employeeDetails["officeOpenTime"] = "09:00" as AnyObject
employeeDetails["officeCloseTime"] = "18:00" as AnyObject
employeeDetails["phoneNumber"] = labelmobile.text as AnyObject
employeeDetails["bloodGroup"] = "A+" as AnyObject
employeeDetails["empId"] = "abcd" as AnyObject
let convertedvalue : String = convertToParameters(employeeDetails)
print("convertedvalues : \(convertedvalue)")
let myUrl = URL(string: "https://us-central1-pickceltest.cloudfunctions.net/rebliss/createNewUser");
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"// Compose a query string
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
let postString = convertedvalue;
print("start create employee")
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil
{
print("error=\(String(describing: error))")
return
}
print("start create employee =successfull")
print("response = \(String(describing: response))")
do {
print("start create employee =parsing problems ")
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
// print("resultjson=one : ",self.json)
print("resultjson=two : ",parseJSON)
}
} catch {
print(error)
}
}
task.resume()
}
function convertToParameters
func convertToParameters(_ params: [String: AnyObject?]) -> String {
var paramList: [String] = []
for (key, value) in params {
guard let value = value else {
continue
}
let scapedKey = key
let scapedValue = value
print("employee add status objects = ","\(scapedKey)=\(scapedValue as AnyObject)")
paramList.append("\(scapedKey)=\(scapedValue as AnyObject)")
}
return paramList.joined(separator: "&")
}
Json Error:
resultjson=two : {
error = {
code = "auth/invalid-phone-number";
message = "The phone number must be a non-empty E.164 standard compliant identifier string.";
};
success = 0;
}
Error in firebase console:
Actual issue with you is your parameters not getting properly url encoded.
Temporary Solution for your code:
In your convertToParams method, make following changes:
let scapedKeyEncoded = scapedKey.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed);
let scapedValueEncoded = scapedValue.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed);
paramList.append("\(scapedKeyEncoded)=\(scapedValueEncoded)"
Note: Your scapedValue must be String, so, make employeeDetails as [String:String].
Permanent Solution(Perfect way for encoding query parameters):
For this, you can change your code to something like this:
Remove convertToParameters method
Add following method in place of it
class func getURLRequestWith(urlStr:String, paramsDict:[String:String]?, isParamsAsQuery:Bool, isParamsAsBody:Bool) -> URLRequest? {
guard var components = URLComponents(string: urlStr) else { return nil }
if paramsDict != nil{
components.queryItems = paramsDict!.map({ (key, value) -> URLQueryItem in
return URLQueryItem(name: key, value: value)
})
}
if isParamsAsQuery{
let request = URLRequest(url: components.url!);
return request;
}
if isParamsAsBody{
let url = URL(string: urlStr);
var request = URLRequest(url: url!);
let bodyStr = components.percentEncodedQuery;
if bodyStr != nil{
request.httpBody = bodyStr!.data(using: String.Encoding.utf8);
}
return request;
}
let url = URL(string: urlStr);
let request = URLRequest(url: url!);
return request;
}
Remove below lines from your code:
let convertedvalue : String = convertToParameters(employeeDetails)
print("convertedvalues : \(convertedvalue)")
let myUrl = URL(string: "https://us-central1-pickceltest.cloudfunctions.net/rebliss/createNewUser");
var request = URLRequest(url:myUrl!)
request.httpBody = postString.data(using: String.Encoding.utf8);
Add following code instead of above removed code
let urlStr = "https://us-central1-pickceltest.cloudfunctions.net/rebliss/createNewUser"
var request = getURLRequestWith(urlStr: urlStr, paramsDict: employeeDetails, isParamsAsQuery: false, isParamsAsBody: true)
convert employeeDetails dictionary to type [String:String] instead of [String:AnyObject]
Now, try your code and it will surely work.
Please check if below solution works. Add percent encoding to you url.
// Create NSURL Ibject
let url : NSString = urlWithParams as NSString
let urlStr = url.addingPercentEncoding( withAllowedCharacters: .urlQueryAllowed)
let searchURL : NSURL = NSURL(string: urlStr! as String)!
// Creaste URL Request
let request = NSMutableURLRequest(url: searchURL as URL, cachePolicy: NSURLRequest.CachePolicy.reloadIgnoringCacheData, timeoutInterval: 120)
// Set request HTTP method to GET. It could be POST as well
request.httpMethod = "POST"

Upload Pdf, Docx and image file to server using Swift 4

I am new to swift, I have been trying to upload pdf, docx and image file from local storage of a Iphone. I have written a code but it doesn't work and I keep getting Status Code: 415 from the response. Here is my code:
func uploadfileToServer(completed: #escaping () -> ()){
let theTitle = labelTitle.text
guard let url = URL(string: "http://www.--------.com/assignment/post") else {return}
var request = URLRequest.init(url: url)
request.httpMethod = "POST"
request.addValue("cf7ab8c9d4efae82b575eabd6bec76cbb86c6108391e036387f3dd5356a582171519367747000", forHTTPHeaderField: "api_key")
let boundary = generateBoundaryString()
// Set Content-Type in HTTP header.
let boundaryConstant = boundary // This should be auto-generated.
let contentType = "multipart/form-data; boundary=" + boundaryConstant
let directory = NSTemporaryDirectory()
let fileName = NSUUID().uuidString
// This returns a URL? even though it is an NSURL class method
let fullURL = NSURL.fileURL(withPathComponents: [directory, fileName])
let fileNamee = fullURL?.path
let mimeType = "text/csv"
let fieldName = "uploadFile"
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
var dataString = "--\(boundaryConstant)\r\n"
dataString += "\r\n"
dataString += "--\(boundaryConstant)--\r\n"
var theBody = Data()
let sectionID : String?
sectionID = nil
let str = "user_id=\(savedsesuid!)" + "&school_id=" + SCHOOL_ID + "&class_id=" + classID + "&section_id=\(sectionID)" + "&subject_id=\(id)"
if let b = str.data(using: .utf8) {
theBody.append(b)
}
let str1 = "&atitle=" + theTitle! + "&class_id=" + classID
if let c = str1.data(using: .utf8){
theBody.append(c)
}
let documentDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
var filePath = documentDirectory.appendingFormat("/")
filePath = filePath.appendingFormat("/Users/prashanna/Desktop/ebusiness_topic_1_handout.pdf")
let pdfData = NSData(contentsOfFile: filePath)
let file = "&afile=" + "\(pdfData)"
if let d = file.data(using: .utf8){
theBody.append(d)
}
print(theBody)
request.httpBody = theBody
URLSession.shared.dataTask(with: request) { (data, response, error) in
print(response)
if let httpResponse = response as? HTTPURLResponse {
let statuscode = httpResponse.statusCode
if statuscode == 401{
self.displayMessage(userMessage: "Sending Failed")
}else if statuscode == 200{
if error == nil{
do{
self.displayMessage(userMessage: "File Successfully Uploaded!")
DispatchQueue.main.async {
completed()
}
}
}
}
}
}.resume()
}
func generateBoundaryString() -> String {
return "Boundary-\(NSUUID().uuidString)"
}
Some solutions tell me to convert the file into Data and then send it to server while some say to directly add the file path to your body.
Need Help!
One fundamental mistake is that you are using dataTask instead of an uploadTask on your URLSession instance, e.g. uploadTask(with:from:completionHandler:)
Construction of Body Data
Here's a generic example from my own code (as requested in the comments below) of how body data might be constructed:
// imagesURLS is an optional array of URLs, i.e. imageURLS:[URL]?
if let imgURLs = imagesURLS {
for f in imgURLs {
let filename = f.lastPathComponent
let splitName = filename.split(separator: ".")
let name = String(describing: splitName.first)
let filetype = String(describing: splitName.last)
let imgBoundary = "\r\n--\(boundary)\r\nContent-Type: image/\(filetype)\r\nContent-Disposition: form-data; filename=\(filename); name=\(name)\r\n\r\n"
if let d = imgBoundary.data(using: .utf8) {
bodyData.append(d)
}
do {
let imgData = try Data(contentsOf:f, options:[])
bodyData.append(imgData)
}
catch {
// can't load image data
}
}
}
let closingBoundary = "\r\n--\(boundary)--"
if let d = closingBoundary.data(using: .utf8) {
bodyData.append(d)
}
The loop means that every item of data (in this case an image) is preceded by a boundary string and after the very last item of data the closing boundary string is added (i.e. the one ending in a double hyphen).
This works for me, in Swift4:
func uploadFiles(_ urlPath: [URL]){
if let url = URL(string: "YourURL"){
var request = URLRequest(url: url)
let boundary:String = "Boundary-\(UUID().uuidString)"
request.httpMethod = "POST"
request.timeoutInterval = 10
request.allHTTPHeaderFields = ["Content-Type": "multipart/form-data; boundary=----\(boundary)"]
for path in urlPath{
do{
var data2: Data = Data()
var data: Data = Data()
data2 = try NSData.init(contentsOf: URL.init(fileURLWithPath: path.absoluteString, isDirectory: true)) as Data
/* Use this if you have to send a JSON too.
let dic:[String:Any] = [
"Key":Value,
"Key":Value
]
for (key,value) in dic{
data.append("------\(boundary)\r\n")
data.append("Content-Disposition: form-data; name=\"\(key)\"\r\n\r\n")
data.append("\(value)\r\n")
}
*/
data.append("------\(boundary)\r\n")
//Here you have to change the Content-Type
data.append("Content-Disposition: form-data; name=\"file\"; filename=\"YourFileName\"\r\n")
data.append("Content-Type: application/YourType\r\n\r\n")
data.append(data2)
data.append("\r\n")
data.append("------\(boundary)--")
request.httpBody = data
}catch let e{
//Your errors
}
DispatchQueue.global(qos: DispatchQoS.QoSClass.userInitiated).sync {
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: { (dataS, aResponse, error) in
if let erros = error{
//Your errors
}else{
do{
let responseObj = try JSONSerialization.jsonObject(with: dataS!, options: JSONSerialization.ReadingOptions(rawValue:0)) as! [String:Any]
}catch let e{
}
}
}).resume()
}
}
}
}
extension Data{
mutating func append(_ string: String, using encoding: String.Encoding = .utf8) {
if let data = string.data(using: encoding) {
append(data)
}
}
}
Sample Code for uploading image is:
func uploadImage(){
var imageToUpload:UIImage = UIImage()
let nsDocumentDirectory = FileManager.SearchPathDirectory.documentDirectory
let nsUserDomainMask = FileManager.SearchPathDomainMask.userDomainMask
let paths = NSSearchPathForDirectoriesInDomains(nsDocumentDirectory, nsUserDomainMask, true)
if let dirPath = paths.first
{
let imageURL = URL(fileURLWithPath: dirPath).appendingPathComponent("Image2.png") //Your image name here
let image = UIImage(contentsOfFile: imageURL.path)
imageToUpload = image!
}
Alamofire.upload(multipartFormData: { (multipartFormData) in
multipartFormData.append(UIImageJPEGRepresentation(imageToUpload, 1)!, withName: "Prescription", fileName: "Profile_Image.jpeg", mimeType: "image/jpeg")
}, to:"you_URL_here")
{ (result) in
switch result {
case .success(let upload, _, _):
print(result)
upload.uploadProgress(closure: { (progress) in
print(progress)
})
upload.responseJSON { response in
//print response.result
print(response);
}
case .failure(let encodingError):
print(encodingError);
}
}
}

Post request with HTTP header parameters

I Want to use Bittrex api. I've read their api docs. There are explanations like the following.
For this version, we use a standard HMAC-SHA512 signing. Append apikey
and nonce to your request and calculate the HMAC hash and include it
under an apisign header.
$apikey='xxx';
$apisecret='xxx';
$nonce=time();
$uri='https://bittrex.com/api/v1.1/market/getopenorders?apikey='.$apikey.'&nonce='.$nonce;
$sign=hash_hmac('sha512',$uri,$apisecret);
$ch = curl_init($uri);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('apisign:'.$sign));
$execResult = curl_exec($ch);
$obj = json_decode($execResult);
I want to do this with Swift. But I don't want to use Alamofire.
I wrote a code. I think I'm doing everything but I'm getting the following error.
{
message = "APISIGN_NOT_PROVIDED";
result = "<null>";
success = 0;
}
I wrote similar code with Delphi. It works fine. So there is no problem with APIKEY. When I use the same parameters in Delphi, the same SecretHex is generated. So there's no problem with Encryption.
I think, I cannot do the Post Request with headers.
I can not find the fault. Would you please help me.
func getBalances()
{
let apiKeySTR = "01235xxxxxx"
let secretSTR = "41691xxxxxx"
let path = "https://bittrex.com/api/v1.1/account/"
let timeInterval = NSDate().timeIntervalSince1970
let epochtime = String(floor(timeInterval))
let urlFull = path + "getbalances" + "?" + "apikey=" + apiKeySTR + "&" + "nonce=" + epochtime
let secretUInt8 : [UInt8] = Array(urlFull.utf8)
var secretKey : [UInt8]?
do {
try secretKey = HMAC(key: secretSTR, variant: .sha512).authenticate(secretUInt8)
} catch {
print ("Error")
}
let secretHex = secretKey?.toHexString() ?? ""
guard let url = URL(string: urlFull) else { return }
var request = URLRequest(url: url)
request.addValue("apising", forHTTPHeaderField: (secretHex))
request.httpMethod = "POST"
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
print(json)
} catch {
print(error)
}
}
}.resume()
}
First off... you have a typo:
request.addValue("apising", forHTTPHeaderField: (secretHex))
I believe it's apisign, not "apising", right?
And below is a recap on creating REST API requests with a header and body. You can update this method according your needs:
1) Create URLRequest
var request = URLRequest(url: requestURL)
2) Set headers and http method:
request.allHTTPHeaderFields = ["Authentication" : "Bearer XYZ..."]
request.httpMethod = "POST"
3) Set request body:
// parameters is a simple [String:String] dictionary, just as header
let jsonData = try? JSONSerialization.data(withJSONObject: parameters)
request.httpBody = jsonData
Complete example:
public enum RESTMethod:String {
case get = "GET"
case post = "POST"
case put = "PUT"
}
public func sendRequest(_ url: String,
method: RESTMethod,
headers: [String : String],
parameters: [String : Any],
completionHandler: #escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionTask! {
let requestURL: URL
if method == .get {
let parameterString = parameters.stringFromHttpParameters()
requestURL = URL(string:"\(url)?\(parameterString)")!
} else {
requestURL = URL(string: url)!
}
var request = URLRequest(url: requestURL)
request.allHTTPHeaderFields = headers
request.httpMethod = method.rawValue
if method == .post {
let jsonData = try? JSONSerialization.data(withJSONObject: parameters)
request.httpBody = jsonData
}
request.timeoutInterval = 60
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
completionHandler(data,response,error)
}
task.resume()
return task
}
extension Dictionary {
/// Build string representation of HTTP parameter dictionary of keys and objects
func stringFromHttpParameters() -> String {
let parameterArray = self.map { (key, value) -> String in
let percentEscapedKey = (key as! String).addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)
let percentEscapedValue = (value as? String ?? "\(value)").addingPercentEncodingForURLQueryValue()!
return "\(percentEscapedKey)=\(percentEscapedValue)"
}
return parameterArray.joined(separator: "&")
}
}
Usage:
sendRequest("http://yourserver",
method: .get, // .post or .put
headers: [],
parameters: [],
completionHandler: { (data, response, error) in
// Handle response here
})

How to escape the HTTP params in Swift

I'm trying to make a post to an HTTP server
By the way this is my code:
func sendPostToUrl(url:String, withParams params: [String: String?] ) {
var request = NSMutableURLRequest(URL: NSURL(string: url)!)
var session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
var err: NSError?
var bodyData = ""
for (key,value) in params{
if (value==nil){ continue }
let scapedKey = key.stringByAddingPercentEncodingWithAllowedCharacters(
.URLHostAllowedCharacterSet())!
let scapedValue = value!.stringByAddingPercentEncodingWithAllowedCharacters(
.URLHostAllowedCharacterSet())!
bodyData += "\(scapedKey)=\(scapedValue)&"
}
request.HTTPBody = bodyData.dataUsingEncoding
(NSUTF8StringEncoding, allowLossyConversion: true)
var task = session.dataTaskWithRequest(request,
completionHandler: {data, response, error -> Void in
println("Response: \(response)")
let dataString = NSString(data: data, encoding: NSUTF8StringEncoding)
println("Data: \(dataString)")
})
task.resume()
}
It works but is not perfect. If I call the function this way:
client.sendPostToUrl("http://novagecko.com/tests/test.php",
withParams: ["hello":"world","inject":"param1=value1&param2=value2"]);
The server detects 3 post fields (with keys hello,inject and param2) instead of 2.
How can I escape the key and values?
Is there something more I could do for improving the method?
If you can target iOS 8 (thanks #Rob), use NSURLComponents to escape your parameters instead:
import Foundation
func encodeParameters(#params: [String: String]) -> String {
var queryItems = map(params) { NSURLQueryItem(name:$0, value:$1)}
var components = NSURLComponents()
components.queryItems = queryItems
return components.percentEncodedQuery ?? ""
}
Now encodeParameters(params:["hello":"world","inject":"param1=value1&param2=value2"]) returns hello=world&inject=param1%3Dvalue1%26param2%3Dvalue2 as you would expect.
Otherwise, the best way to create the character set that will let you escape your values properly is this:
var safeCharacterSet = NSCharacterSet.URLQueryAllowedCharacterSet().mutableCopy()
safeCharacterSet.removeCharactersInString("&=")
and see #rintaro's answer to use filter/map properly to perform the encoding in a nice way.
It seems, NSCharacterSet doesn't have relevant set for that.
So, add this
extension NSCharacterSet {
class func URLUnreservedCharacterSet() -> NSCharacterSet {
return self(charactersInString: "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_.~")
}
}
Then
var bodyData = ""
var safeCharacterSet = NSCharacterSet.URLUnreservedCharacterSet()
for (key,value) in params{
if (value==nil){ continue }
let scapedKey = key.stringByAddingPercentEncodingWithAllowedCharacters(safeCharacterSet)!
let scapedValue = value!.stringByAddingPercentEncodingWithAllowedCharacters(safeCharacterSet)!
bodyData += "\(scapedKey)=\(scapedValue)&"
}
As following #Rob's advice in comment, here is a map and join example:
let params:[String:String?] = ["fubar":nil, "hello":"world", "inject":"param1=value1&param2=value2"]
let safeCharacterSet = NSCharacterSet.URLUnreservedCharacterSet()
let pairs = filter(params, {$1 != nil}).map { (key, value) -> String in
let _key = key.stringByAddingPercentEncodingWithAllowedCharacters(safeCharacterSet)!
let _val = value!.stringByAddingPercentEncodingWithAllowedCharacters(safeCharacterSet)!
return _key + "=" + _val
}
let bodyData = "&".join(pairs)
This is better because there is no trailing & in the result.

Resources