40

The back end developer had given these instructions in POST requests:

  1. Route: {url}/{app_name/{controller}/{action}
  2. The controller and action should be on small caps.
  3. API test link: http:****************
  4. Request should be use POST Method.
  5. Parameters should be pass via request content body (FormUrlEncodedContent).
  6. Parameters should be on json format.
  7. Parameters are key sensitive.

Having no experience with number 5 in the protocol, I searched and ended with my code.

-(id)initWithURLString:(NSString *)URLString withHTTPMEthod:(NSString *)method withHTTPBody:(NSDictionary *)body {

    _URLString = URLString;
    HTTPMethod = method;
    HTTPBody = body;

    //set error message
    errorMessage = @"Can't connect to server at this moment. Try again later";
    errorTitle = @"Connection Error";

    return  self;
}


-(void)fireConnectionRequest {

    NSOperationQueue *mainQueue = [[NSOperationQueue alloc] init];
    [mainQueue setMaxConcurrentOperationCount:5];

    NSError *error = Nil;

    NSURL *url = [NSURL URLWithString:_URLString];
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];

    NSData *sendData = [NSJSONSerialization dataWithJSONObject:HTTPBody options:NSJSONWritingPrettyPrinted error:&error];
    [request setHTTPMethod:@"POST"];

    [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
    [request setValue:@"application/json" forHTTPHeaderField:@"Accept"];

    [request setHTTPBody: sendData];
    [NSURLConnection connectionWithRequest:request delegate:self];

    NSString *jsonString = [[NSString alloc]initWithData:sendData encoding:NSUTF8StringEncoding];


    //fire URL connectiion request
    [NSURLConnection sendAsynchronousRequest:request queue:mainQueue completionHandler:^(NSURLResponse *response, NSData *responseData, NSError *error) {

        //get the return message and transform to dictionary
        NSString *data = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
        returnMessage = [NSJSONSerialization JSONObjectWithData: [data dataUsingEncoding:NSUTF8StringEncoding]
                                                        options: NSJSONReadingMutableContainers
                                                          error:&error];


        //check return message
        if (!error) {
            [delegate returnMessageForTag:self.tag];

        }
        else {
            [delegate returnErrorMessageForTag:self.tag];
        }

    }];

}

I pass a dictionary formatted to JSON. he agrees that I was able to pass the right data. And I was able to connect to the API, but it is always returning "FAILED" when I try send data for registration. There are no problems in connection, but I failed to transfer the data.

The android developer here using the same API has no problem with it, but wasn't able to help me out since he's not familiar with iOS.

What am I missing?

jmac
  • 574
  • 1
  • 5
  • 11
  • 2
    In some cases I had a problem related to the ORDER of the JSON parameters. Since NSDictionary is unordered, when you convert it into JSON the order of the parameter can be different from the order you used when declaring it. Try to setup the JSON string manually and check if it works. If the problem is this, you have to use an ordered dictionary (search on GitHub, there are many implementations) – LombaX Feb 27 '14 at 09:26

11 Answers11

49

Try like this code

Objective C

NSString *post =[NSString stringWithFormat:@"AgencyId=1&UserId=1&Type=1&Date=%@&Time=%@&Coords=%@&Image=h32979`7~U@)01123737373773&SeverityLevel=2",strDateLocal,strDateTime,dict];
NSData *postData = [post dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postLength = [NSString stringWithFormat:@"%d",[postData length]];
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
[request setURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://google/places"]]];
[request setHTTPMethod:@"POST"];
[request setValue:postLength forHTTPHeaderField:@"Content-Length"];
[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
[request setHTTPBody:postData];
NSError *error;
NSURLResponse *response;
NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSString *str=[[NSString alloc]initWithData:urlData encoding:NSUTF8StringEncoding];

Swift 2.2

var post = "AgencyId=1&UserId=1&Type=1&Date=\(strDateLocal)&Time=\(strDateTime)&Coords=\(dict)&Image=h32979`7~U@)01123737373773&SeverityLevel=2"
var postData = post.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: true)!
var postLength = "\(postData.length)"
var request = NSMutableURLRequest()
request.URL = NSURL(string: "http://google/places")!
request.HTTPMethod = "POST"
request.setValue(postLength, forHTTPHeaderField: "Content-Length")
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.HTTPBody = postData
NSError * error
NSURLResponse * response
var urlData = try! NSURLConnection.sendSynchronousRequest(request, returningResponse: response)!
var str = String(data: urlData, encoding: NSUTF8StringEncoding)

Swift 3.0

let jsonData = try? JSONSerialization.data(withJSONObject: kParameters)
    let url: URL = URL(string: "Add Your API URL HERE")!
    print(url)
    var request: URLRequest = URLRequest(url: url)
    request.httpMethod = "POST"
    request.httpBody = jsonData
    request.setValue(Constant.UserDefaults.object(forKey: "Authorization") as! String?, forHTTPHeaderField: "Authorization")
    request.setValue(Constant.kAppContentType, forHTTPHeaderField: "Content-Type")
    request.setValue(Constant.UserAgentFormat(), forHTTPHeaderField: "User-Agent")

    let task = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in

        if data != nil {

            do {
                let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! NSDictionary
                print(json)
            } catch let error as NSError {
                print(error)
            }
        } else {
            let emptyDict = NSDictionary()
        }
    })
    task.resume()

Swift 4

let headers = [
            "Content-Type": "application/x-www-form-urlencoded"
        ]

    let postData = NSMutableData(data: "UserID=351".data(using: String.Encoding.utf8)!)
    let request = NSMutableURLRequest(url: NSURL(string: "Add Your URL Here")! as URL,
                                      cachePolicy: .useProtocolCachePolicy,
                                      timeoutInterval: 10.0)
    request.httpMethod = "POST"
    request.allHTTPHeaderFields = headers
    request.httpBody = postData as Data

    let session = URLSession.shared
    let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
        if (error != nil) {
            print(error!)
        } else {
            let httpResponse = response as? HTTPURLResponse
            print(httpResponse!)

            do {
                let json = try JSONSerialization.jsonObject(with: data!, options: .allowFragments)
                print(json)
            } catch {
                print(error)
            }

        }
    })

    dataTask.resume()

Alamofire

Alamofire.request("Add Your URL Here",method: .post, parameters: ["CategoryId": "15"])
        .validate(contentType: ["application/x-www-form-urlencoded"])
        .responseJSON { (response) in

            print(response.result.value)

    }

I hope this code useful for you.

Darshan Kunjadiya
  • 3,323
  • 1
  • 29
  • 31
  • Thanks.. it worked for me..!! Don't know why the answer is not marked correct. – Amrendra Pratap Singh Nov 22 '17 at 07:55
  • 10
    The Swift 3 code sample is wrong. It isn't doing application/x-www-form-urlencoded at all, it's sending JSON. – alexkent Sep 28 '18 at 12:12
  • Please add example for Alamofire also. Thanks – Awais Fayyaz Oct 01 '18 at 07:05
  • @AwaisFayyaz Sure I will do it soon. Thanks – Darshan Kunjadiya Oct 01 '18 at 09:23
  • 1
    @AwaisFayyaz As per your request I have Added Alamofire example in the answer. – Darshan Kunjadiya Mar 22 '19 at 11:00
  • Can anyone please tell me , how to create api request parameter like ------ CommunityID:24 FormID:21 FormData:{"FormID":"21","userResponse":[{"FormFieldID":"FormFieldID1","RptHeader":"Name","userData":"mahesh"},{"FormFieldID":"FormFieldID2","RptHeader":"Mobile Number","userData":"7788556699"},{"FormFieldID":"FormFieldID3","RptHeader":"Email","userData":"mahesh@gmail.com"},{"FormFieldID":"FormFieldID3","RptHeader":"Position","userData":"tester"},{"FormFieldID":"FormFieldID3","RptHeader":"Employment Status","userData":"employee"}]} – Protocol Jul 02 '20 at 15:17
13

Swift 4

let params = ["password":873311,"username":"jadon","client_id":"a793fb82-c978-11e9-a32f-2a2ae2dbcce4"]
let jsonString = params.reduce("") { "\($0)\($1.0)=\($1.1)&" }.dropLast()
let jsonData = jsonString.data(using: .utf8, allowLossyConversion: false)!
urlRequest.addValue("application/x-www-form-urlencoded", forHTTPHeaderField:"Content-Type")
urlRequest.httpBody  = jsonData 
Jiffin
  • 405
  • 5
  • 8
7

@fatihyildizhan

not enough reputation to directly comment your answer therefore this answer.

Swift 1.2

let myParams = "username=user1&password=12345"
let postData = myParams.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: true)
let postLength = String(format: "%d", postData!.length)

var myRequest = NSMutableURLRequest(URL: self.url)
myRequest.HTTPMethod = "POST"
myRequest.setValue(postLength, forHTTPHeaderField: "Content-Length")
myRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
myRequest.HTTPBody = postData

var response: AutoreleasingUnsafeMutablePointer<NSURLResponse?> = nil

This code above just works fine in my case.

Muarl
  • 1,977
  • 1
  • 14
  • 18
  • This answer doesn't correctly url encode parameters, so data which includes the reserved characters may be rejected or misinterpreted by the server. See spec here: https://url.spec.whatwg.org/#urlencoded-serializing – Josh Heald Mar 07 '20 at 12:19
7

Swift does offer a function for URL-%-encoding, but it is not an exact match as noted by @nolanw in the 1st comment. For Step 5 in the original question, once you have the key-value pairs in some structure, here is a short and simple alternative for encoding (Swift 4.2):

var urlParser = URLComponents()
urlParser.queryItems = [
    URLQueryItem(name: "name", value: "Tim Tebow"),
    URLQueryItem(name: "desc", value: "Gators' QB")
]
let httpBodyString = urlParser.percentEncodedQuery

Paste that into an Xcode playground, and add print(httpBodyString!). In the output, you will see:

name=Tim%20Tebow&desc=Gators'%20QB

Note: This is for percent-encoding set of basic form values (i.e. not binary data and not multi-part)

Collierton
  • 539
  • 5
  • 10
  • 4
    This is a great idea, but doesn't quite match the [spec for `application/x-www-form-urlencoded` serializing](https://url.spec.whatwg.org/#urlencoded-serializing); spaces should be encoded as `+` but with `URLComponents.percentEncodedQuery` spaces are encoded as `%20`. This might not make a difference for your use case, but I figured it was worth pointing out in case it affects someone else's! – nolanw Mar 06 '19 at 04:16
4

This version handles encoding of parameters and replacing spaces with '+'.

extension String {
    static let formUrlencodedAllowedCharacters =
        CharacterSet(charactersIn: "0123456789" +
            "abcdefghijklmnopqrstuvwxyz" +
            "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
            "-._* ")

    public func formUrlencoded() -> String {
        let encoded = addingPercentEncoding(withAllowedCharacters: String.formUrlencodedAllowedCharacters)
        return encoded?.replacingOccurrences(of: " ", with: "+") ?? ""
    }
}

class HTTPUtils {
    public class func formUrlencode(_ values: [String: String]) -> String {
        return values.map { key, value in
            return "\(key.formUrlencoded())=\(value.formUrlencoded())"
        }.joined(separator: "&")
    }
}

let headers = [
    "content-type": "application/x-www-form-urlencoded; charset=utf-8"
]

let body = HTTPUtils.formUrlencode([
    "field": "value"
])

var request = try URLRequest(url: url, method: .post, headers: headers)
request.httpBody = body.data(using: .utf8)

URLSession.shared.dataTask(with: request, completionHandler: { ... }).resume()
Zmey
  • 2,304
  • 1
  • 24
  • 40
  • This answer correctly handles percent encoding, using the mechanism specified here: https://url.spec.whatwg.org/#urlencoded-serializing The url encoding (often lacking, if present, incorrect) in the other answers available to this question doesn't cover the correct characters. When using those options, more complicated data in the body will be incorrectly encoded, and so the receiving server won't understand the payload. It's a little surprising there's nothing built in to `URLRequest` to handle this. – Josh Heald Mar 07 '20 at 12:09
  • AFAICT the only adequate answer here. Though would be better as an extension on [String: String]. And you could use `"""` with backslashes to avoid the ugly `+` in the allowed character variable. – mxcl Sep 09 '21 at 02:26
2

With Swift 3, let jsonData = try? JSONSerialization.data(withJSONObject: kParameters) didn't work fine for me, so i had to copy the AlamoFire solution...

let body2 = ["username": "au@gmail.com",
        "password": "111",
        "client_secret":"7E",
        "grant_type":"password"]

let data : Data = query(body2).data(using: .utf8, allowLossyConversion: false)!
var request : URLRequest = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField:"Content-Type");
request.setValue(NSLocalizedString("lang", comment: ""), forHTTPHeaderField:"Accept-Language");
request.httpBody = data 

do {...}

}

public func queryComponents(fromKey key: String, value: Any) -> [(String, String)] {
        var components: [(String, String)] = []

        if let dictionary = value as? [String: Any] {
            for (nestedKey, value) in dictionary {
                components += queryComponents(fromKey: "\(key)[\(nestedKey)]", value: value)
            }
        } else if let array = value as? [Any] {
            for value in array {
                components += queryComponents(fromKey: "\(key)[]", value: value)
            }
        } else if let value = value as? NSNumber {
            if value.isBool {
                components.append((escape(key), escape((value.boolValue ? "1" : "0"))))
            } else {
                components.append((escape(key), escape("\(value)")))
            }
        } else if let bool = value as? Bool {
            components.append((escape(key), escape((bool ? "1" : "0"))))
        } else {
            components.append((escape(key), escape("\(value)")))
        }

        return components
    }


    public func escape(_ string: String) -> String {
        let generalDelimitersToEncode = ":#[]@" // does not include "?" or "/" due to RFC 3986 - Section 3.4
        let subDelimitersToEncode = "!$&'()*+,;="

        var allowedCharacterSet = CharacterSet.urlQueryAllowed
        allowedCharacterSet.remove(charactersIn: "\(generalDelimitersToEncode)\(subDelimitersToEncode)")

        var escaped = ""

        if #available(iOS 8.3, *) {
            escaped = string.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? string
        } else {
            let batchSize = 50
            var index = string.startIndex

            while index != string.endIndex {
                let startIndex = index
                let endIndex = string.index(index, offsetBy: batchSize, limitedBy: string.endIndex) ?? string.endIndex
                let range = startIndex..<endIndex

                let substring = string.substring(with: range)

                escaped += substring.addingPercentEncoding(withAllowedCharacters: allowedCharacterSet) ?? substring

                index = endIndex
            }
        }
        return escaped
    }

And one extension:

extension NSNumber {
fileprivate var isBool: Bool { return CFBooleanGetTypeID() == CFGetTypeID(self) }}

This is temporary, It has to be a better solution...

Hope it help...

Ted
  • 22,696
  • 11
  • 95
  • 109
Diego Renau
  • 109
  • 3
  • 1
    in this line ```let data : Data = query(body2).data(using: .utf8, allowLossyConversion: false)!``` what is query() ? i don't find it in your code ! – Basel Jan 15 '19 at 20:10
  • This answer doesn't correctly url encode parameters, so data which includes the reserved characters may be rejected by the server. See spec here: https://url.spec.whatwg.org/#urlencoded-serializing – Josh Heald Mar 07 '20 at 12:17
2

Swift 4.2

    func percentEscapeString(_ string: String) -> String {
            var characterSet = CharacterSet.alphanumerics
            characterSet.insert(charactersIn: "-._* ")
            return string
              .addingPercentEncoding(withAllowedCharacters: characterSet)!
              .replacingOccurrences(of: " ", with: " ")
              .replacingOccurrences(of: " ", with: " ", options: [], range: nil)
              .replacingOccurrences(of: "\"", with: "", options: NSString.CompareOptions.literal, range:nil)
          }
//    Set encoded values to Dict values you can decode keys if required
    dictData.forEach { (key, value) in
              if let val = value as? String {
                dictData[key] = self.percentEscapeString(val)
              } else {
                dictData[key] = value
              }
            }

This worked for me and here is link of source https://gist.github.com/HomerJSimpson/80c95f0424b8e9718a40

Gurjinder Singh
  • 9,221
  • 1
  • 66
  • 58
  • This answer correctly handles percent encoding, using the mechanism specified here: url.spec.whatwg.org/#urlencoded-serializing The url encoding (often lacking, if present, incorrect) in the other answers available to this question doesn't cover the correct characters. When using those options, more complicated data in the body will be incorrectly encoded, and so the receiving server won't understand the payload. It's a little surprising there's nothing built in to URLRequest to handle this. – Josh Heald Mar 07 '20 at 12:15
1

As of Swift 5, the following code has been tested to work today.

let url = URL(string: "https://something")
var request = URLRequest(url: url!)
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpMethod = "POST"
let post = "parameter1=abc&parameter2=abc"
let postData = post.data(using: String.Encoding.ascii, allowLossyConversion: true)!
request.httpBody = postData
0

Is it possible to convert this code to swift ? I already tried but could not handle it. Maybe this code block may help you. Thanks.

    let myParams:NSString = "username=user1&password=12345"
    let myParamsNSData:NSData = NSData(base64EncodedString: myParams, options: NSDataBase64DecodingOptions.IgnoreUnknownCharacters)!
    let myParamsLength:NSString = NSString(UTF8String: myParamsNSData.length)
    let myRequest: NSMutableURLRequest = NSURL(fileURLWithPath: self.url)
    myRequest.HTTPMethod = "POST"
    myRequest.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
    myRequest.HTTPBody = myParamsNSData
    var data2: NSData!
    var error2: NSError!
Ted
  • 22,696
  • 11
  • 95
  • 109
fatihyildizhan
  • 8,614
  • 7
  • 64
  • 88
0

Parse dictionary params to string:

-(NSData *)encodeParameters:(NSDictionary *)parameters {

NSMutableArray *list = [NSMutableArray new];

for (NSString *key in [parameters allKeys]) {
    id obj = [parameters objectForKey:key];
    NSString *path = [NSString stringWithFormat:@"%@=%@", key, obj];
    [list addObject:path];
}

return [[list componentsJoinedByString:@"&"] dataUsingEncoding:NSUTF8StringEncoding];

}

And use it:

[request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:[self encodeParameters:parameters]];
-1
let params:[String: Any]
if "application/x-www-form-urlencoded" {
let bodyData = params.stringFromHttpParameters()
self.request.httpBody = bodyData.data(using: String.Encoding.utf8)}
if "application/json"{
  do {
    self.request.httpBody = try JSONSerialization.data(withJSONObject: params, options: JSONSerialization.WritingOptions())
  } catch {
    print("bad things happened")
  }
}

extension Dictionary

func stringFromHttpParameters() -> String {
let parameterArray = self.map { (key, value) -> String in let percentEscapedKey = (key as!String).stringByAddingPercentEncodingForURLQueryValue()!

let percentEscapedValue = (value as! String).stringByAddingPercentEncodingForURLQueryValue()!}
return "\(percentEscapedKey)=\(percentEscapedValue)"}
return parameterArray.joined(separator: "&")}
Ted
  • 22,696
  • 11
  • 95
  • 109