Swift 3 reusable post method in helper - ios

I am working on swift 3 application and want to build login system using REST API. First I wanted a way to post data to server (PHP + MYSQL) with parameters so I found this post.
HTTP Request in Swift with POST method
Now I wanted place this code in a method as helper so I can utilise this method from anywhere in app. Hence followed this way:
Where to put reusable functions in IOS Swift?
Current code is as follow:
import Foundation
class Helper {
static func postData(resource: String, params: [String: String]) -> [String:String] {
var request = URLRequest(url: URL(string: "http://localsite.dev/api/\(resource)")!)
request.httpMethod = "POST"
var qryString: String = "?key=abcd"
for (paramKey, paramVal) in params {
qryString = qryString.appending("&\(paramKey)=\(paramVal)")
}
request.httpBody = qryString.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("Error")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("Error on HTTP")
return
}
let responseString = String(data: data, encoding: .utf8)
print("success and here is returned data \(responseString)")
}
task.resume()
return ["data" : "some data"]
}
}
Call this using
let loginDetails: [String: String] = ["email": emailTxt.text!, "pass": passTxt.text!]
Helper.postData(resource: "login", params: loginDetails)
In above method rather then printing data I want to return data as per below 4 conditions.
1.If error in request data then I want to return as
[“status”: false, “message”: “Something wrong with request”]
2.If error in HTTP request
[“status”: false, “message”: “Resource not found”]
3.If login fail
[“status”: false, “message”: “Wrong login details”]
4.If login success
[“status”: true, “message”: “Login success”]

If you want to use a third party library for handling HTTP request, I strongly recommend Alamofire.
When I wanna handle HTTP requests I usually create a singleton class:
class HttpRequestHelper {
static let shared = HttpRequestHelper()
func post(to url: URL, params: [String: String], headers: [String: String], completion: (Bool, String) -> ()){
//Make the http request
//if u got a successful response
// parse it to JSON and return it via completion handle
completion(true, message)
//if response is not successful
completion(false, message)
}
}
And you can use it everywhere:
class AnotherClass: UIViewController {
HttpRequestHelper.shared.post(to: url, params: [:], header: [:],
completion:{
success, message in
print(success)
print(message)
})
}
To the POST method more reusable and not just specific to an endpoint, I usually make the completion handler params as Bool, JSON. And then I handle the JSON response from wherever I call the method.
Oh and I use SwiftyJson to parse json, it's the simplest.

Related

Swift Alamofire + OAuth2 refresh token

Currently learning how to add OAuth2 via Alamofire and getting confused. I'm using the password grant type and when a request fails I understand that the retrier kicks in and requests a token refresh. The confusing part is which one do I use?
Alamofire 4 using p2/OAuth2
Alamofire RequestRetrier + Request Adapter
The first one uses less code so unsure if its all the functionality I need. I also can't find a concrete example explaining this process.
I believe the following performs the refresh request?
private func refreshTokens(completion: RefreshCompletion) {
guard !isRefreshing else { return }
isRefreshing = true
let urlString = "\(baseURLString)/oauth2/token"
let parameters: [String: Any] = [
"access_token": accessToken,
"refresh_token": refreshToken,
"client_id": clientID,
"grant_type": "refresh_token"
]
sessionManager.request(urlString, withMethod: .post, parameters: parameters, encoding: .json).responseJSON { [weak self] response in
guard let strongSelf = self else { return }
if let json = response.result.value as? [String: String] {
completion(true, json["access_token"], json["refresh_token"])
} else {
completion(false, nil, nil)
}
strongSelf.isRefreshing = false
}
}
This is then passed back to adapt the previous request?
func should(_ manager: SessionManager, retry request: Request, with error: Error, completion: #escaping RequestRetryCompletion) {}
Is this the correct way of implementing this?
Thanks

How to send an image via a POST request in Swift 4?

func createPost(username: String, user_id: String, description: String, image: UIImage, completion: #escaping (Bool) -> ()) {
var request = URLRequest(url: URL(string: Globals.root+"/amazing/image/post/here")!)
request.httpMethod = "POST"
//request.httpBody = .data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
if let data = data {
do {
let aaData = try JSONSerialization.jsonObject(with: data, options: [])
if let ppData = aaData as? [String:Any] {
let u = ppData["username"] as! Bool
if u == true {
completion(true)
}
}
} catch let error as NSError {
print(error.localizedDescription)
completion(false)
}
}
}
task.resume()
}
I've been searching and searching for a valid answer and I haven't found any that pertains to Swift 4. I want to be able to send an image to my RESTful API without encoding it. How can one do that? Thanks in advance. : )
First off, given the fact you're sending username, and stuff along with image, it must be a multipart form (or some ugly BASE64 encoded JSON property).
Typically speaking, clean way to do this is upload image first via binary body post (as the image is the sole thing in transfer and is faster too), obtain the Id and reference it as a field.
Failing this, it's quite hard, how about using Moya + AlamoFire?
https://github.com/Moya/Moya/issues/598
An image is a binary object, and this not valid HTTP unless you either encode it via multipart form or wrap it JSON safe Base-64 (assuming you are using JSON)

Alamofire response not matching request

I am having an issue when making a POST request to my API via Alamofire, GETs work without issue, however whenever I make a POST when I check the response I get the results of the last GET.
import Alamofire
import SwiftyJSON
class NetworkManager {
static let sharedInstace = NetworkManager()
let defaultManager: Alamofire.Manager = {
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"homestead.app": .DisableEvaluation
]
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = Alamofire.Manager.defaultHTTPHeaders
return Alamofire.Manager(
configuration: configuration,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
}()
}
internal class ApiHelper {
/**
Get data from a target URL and return JSON data to be parsed
- parameter targetURL: URL to pull data from
- parameter success: return data to the calling function
- parameter failure: return an error message to the calling function
*/
private func getDataFromAPI(targetURL: String, success:(JSONData: JSON) -> (), failure:(message: String) -> ()) {
NetworkManager.sharedInstace.defaultManager.request(.GET, targetURL).responseJSON { response in
print(response.result)
switch response.result {
case .Success:
if let jsonRaw = response.result.value {
let json = JSON(jsonRaw)
success(JSONData: json)
}
case .Failure(let error):
print(error.localizedDescription)
failure(message: error.localizedDescription)
}
}
}
/**
Post data to the target URL and return errors as JSON data to be parsed
- parameter targetURL: URL to post to
- parameter parameters: JSON data to post
- parameter success: return success message to the calling function
- parameter failure: return JSON data to the calling function with server error
*/
private func postDataToAPI(targetURL: String, parameters: [String : AnyObject], success:() -> (), failure:(JSONData: JSON) -> ()) {
NetworkManager.sharedInstace.defaultManager.request(.POST, targetURL, parameters: parameters, encoding: .JSON).responseJSON { response in
debugPrint(response)
success()
}
}
/**
Post an updated profile to the API
- parameter parameters: JSON data to be posted
- parameter success: success callback
- parameter failure: JSON data of serverError
*/
internal func postUpdateRequest(parameters: [String : AnyObject], success:() -> (), failure:(JSONData: JSON) -> ()) {
let url = "https://homestead.app/profile/a/update"
postDataToAPI(url, parameters: parameters, success: {
success()
}, failure: { JSONData in
failure(JSONData: JSONData)
})
}
/**
Get all states from the API
- parameter success: JSON data of all states
- parameter failure: failure message
*/
internal func getAllStates(success:(JSONData: JSON) -> (), failure:(message: String) -> ()) {
let url = "https://homestead.app/api/state/all"
getDataFromAPI(url, success:
{ JSONData in
success(JSONData: JSONData)
}, failure: { message in
failure(message: message)
})
}
}
let api = ApiHelper()
api.getAllStates({ JSONdata in
print(JSONdata)
let params: [String : AnyObject] = ["name" : "Bob"]
api.postUpdateRequest(params, success: { JSONdata in
print("Success")
}, failure: { message in
print("Message")
})
}, failure: { message in
print(message)
})
My code first gets the list of states and then posts an updated user profile. My issue is in that when I get the response for that updated user profile, it includes the response from the earlier GET request that had already been completed. The POST goes through and the changes are made in the web services, but I have no indications in my response object.
I have confirmed the server does not return the list of states when making a POST request, it returns below when called manually from the browser:
{
"success": "Changes saved!"
}
I'm at a loss on why I am getting a response from an earlier request from my POST. Any thoughts?
I figured this out. It turned out I had to add "X-Requested-With": "XMLHttpRequest" to the requests header:
configuration.HTTPAdditionalHeaders = [
"X-Requested-With": "XMLHttpRequest"
]
Now I am getting the responses correctly from the server.

Data not being returned in Swift function iOS [duplicate]

This question already has answers here:
I won't be able to return a value with Alamofire in Swift
(3 answers)
Closed 7 years ago.
Possibly this could be a misunderstanding that I have in Swift. I'm new to the language and my background is in Perl, which makes me feel like swift is acting differently.
I have 2 files. ViewController.swift and APICalls.swift. In viewcontroller I have a function for a button. In this function I'm making a call to another function in the APICalls. When I do, my println() in the APICalls is printing the correct data, however, it's doing so after my println() in the button function.
Viewcontroller.swift
#IBAction func buttonStuff(sender: AnyObject) {
var api = APICalls()
var token:String
token = api.TEST("letmein")
println("\ntokenDidLOAD = \(token)\n")
}
APICalls.swift
class APICalls {
func TEST(command: String) -> (String) {
var token:String = ""
// Form URL-Encoded Body
let bodyParameters = [
"COMMAND":command,
]
let encoding = Alamofire.ParameterEncoding.URL
// Fetch Request
Alamofire.request(.POST, "http://api.com/?v=1", parameters: bodyParameters, encoding: encoding)
.validate(statusCode: 200..<300)
.responseJSON{(request, response, data, error) in
if (error == nil)
{
let json = JSON(data!)
token = json["TOKEN"].string!
println("jsonAPI = \(json) \n\n")
}
else
{
println("HTTP HTTP Request failed: \(error)")
}
}
return token
}
}
Here is my output
tokenDidLOAD =
jsonAPI = {
"STATUS" : "OK",
"TOKEN" : "698798765432134654",
}
I don't understand why 'tokenDidLOAD' is printing first before the jsonAPI.
Because the request that you make is asynchronous. You first return the token that is not present yet and only after that the request is actually finished. You don't need to get the token as TEST function's return value. Your TEST should be like this:
func TEST(command: String, completion:(String)->()) {
var token:String = ""
// Form URL-Encoded Body
let bodyParameters = [
"COMMAND":command,
]
let encoding = Alamofire.ParameterEncoding.URL
// Fetch Request
Alamofire.request(.POST, "http://api.com/?v=1", parameters: bodyParameters, encoding: encoding)
.validate(statusCode: 200..<300)
.responseJSON{(request, response, data, error) in
if (error == nil)
{
let json = JSON(data!)
token = json["TOKEN"].string!
println("jsonAPI = \(json) \n\n")
completion(token)
}
else
{
println("HTTP HTTP Request failed: \(error)")
}
}
Then you call it:
api.TEST("letmein", {(newToken : String) in
token = newToken
println("\ntokenDidLOAD = \(token)\n")
})
Because this println("jsonAPI = \(json) \n\n") is in side the block statement, which will run as soon request get complete, till then compiler will run next jobs means your println("\ntokenDidLOAD = \(token)\n"). Block will Run on another subthread.
If you want to print println("\ntokenDidLOAD = \(token)\n") when request get complete, then use oncomplete based block.
May this help you bit a way.
Alamofire fetches data asynchronously by default, which is the desired behaviour for pretty much every website or app. By the time the function reaches the return token the request might not have been finished yet.
One idea is to pass in a closure to your API call to be executed once the request is finished:
func TEST(command: String, closure: (JSON) -> ()) -> (String) {
var token:String = ""
// Form URL-Encoded Body
let bodyParameters = [
"COMMAND":command,
]
let encoding = Alamofire.ParameterEncoding.URL
// Fetch Request
Alamofire.request(.POST, "http://api.com/?v=1", parameters: bodyParameters, encoding: encoding)
.validate(statusCode: 200..<300)
.responseJSON{(request, response, data, error) in
if (error == nil)
{
let json = JSON(data!)
token = json["TOKEN"].string!
println("jsonAPI = \(json) \n\n")
closure(json)
}
else
{
println("HTTP HTTP Request failed: \(error)")
}
}
return token
}
Then you could call it via:
api.TEST("letmein") { json in
let token = json["TOKEN"].stringValue
println("\ntokenDidLOAD = \(token)\n")
}
The closure accepts an object of type JSON as a parameter, which you can use to do whatever you want with the json inside.
Also one thing to note: I'm assuming you're using the SwiftyJSON library. Before, you called json["TOKEN"].string!. This force-unwraps an optional, which should be avoided generally (unless you're absolutely sure it has a value). In such a case, you would want to use json["TOKEN"].stringValue instead. It returns String instead of String?

Perform POST request in iOS Swift

I am trying perform a POST request and the request does not go through. I have looked through Perform POST request in Swift already but it does not contain what I'm looking for.
func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
var request = NSMutableURLRequest(URL: NSURL(string: "https://us1.lacunaexpanse.com"))
println("request url https://us1.lacunaexpanse.com")
var session = NSURLSession.sharedSession()
request.HTTPMethod = "POST"
let apikey = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
println("apikey",apikey)
let username = "username"
let password = "password"
var login = Array(["username", "password", "apikey"])
let jsonDictionary = ["2.0", "jsonrpc", "1", "id", "login", "method", "login", "params"]
println("jsonDictionary",jsonDictionary)
var writeError: NSError?
let jsonData = NSJSONSerialization.dataWithJSONObject(jsonDictionary, options: NSJSONWritingOptions(), error: NSErrorPointer())
var resultAsString = NSString(data: jsonData, encoding: NSUTF8StringEncoding)
resultAsString = resultAsString.stringByAppendingString("empire")
let url = NSURL.URLWithString("string")
println("url",url)
var request2 = NSMutableURLRequest()
println("Post url =%#",url)
request2 = NSMutableURLRequest(URL:url)
request2.HTTPMethod = "POST"
var connection = NSURLConnection(request: request, delegate: self, startImmediately: false)
return true
There are a whole bunch of tactical issues here:
You're creating URLSession, but then issuing NSURLConnection request. Just use URLSession.
Your “request dictionary” isn't a dictionary, but rather an array. For example, to issue the JSON-RPC request, the proper format of the dictionary is:
let requestDictionary: [String: Any] = [
"jsonrpc" : "2.0",
"id" : 1,
"method" : "login",
"params" : ["myuserid", "mypassword", "mykey"]
]
Minor issue, but you are using a lot of variables (via var) where a constant (via let) would be fine. In the spirit of Swift’s safety, use let wherever possible.
According to the Lacuna Expanse API, your URL should be including the module name.
So, for example if doing POST requests in the "Empire" module, the URL is:
let url = URL(string: "https://us1.lacunaexpanse.com/empire")!
You're likely to be doing a lot of requests, so I'd suggest putting the bulk of that in a single function that you can call again and again, without repeating code all over the place. Perhaps a function like the following that takes the following parameters:
module (e.g. “empire” vs “alliance”);
method (e.g. “login” vs “fetch_captcha”);
the parameters appropriate for that request (e.g. for “login”, that would be the “name”, “password”, and the “api_key”); and
closure that will be called when the asynchronous request finishes.
This function then prepares the JSON-RPC request and calls the closure when the request finishes:
#discardableResult
func submitLacunaRequest(module: String, method: String, parameters: Any, completion: #escaping (Result<[String: Any], Error>) -> Void) -> URLSessionTask? {
let session = URLSession.shared
let url = URL(string: "https://us1.lacunaexpanse.com")!
.appendingPathComponent(module)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json-rpc", forHTTPHeaderField: "Content-Type")
let requestDictionary: [String: Any] = [
"jsonrpc": "2.0",
"id" : 1,
"method" : method,
"params" : parameters
]
request.httpBody = try! JSONSerialization.data(withJSONObject: requestDictionary)
let task = session.dataTask(with: request) { data, response, error in
// handle fundamental network errors (e.g. no connectivity)
guard error == nil, let data = data else {
completion(.failure(error ?? URLError(.badServerResponse)))
return
}
// check that http status code was 200
guard
let httpResponse = response as? HTTPURLResponse,
200 ..< 300 ~= httpResponse.statusCode
else {
completion(.failure(URLError(.badServerResponse)))
return
}
// parse the JSON response
do {
guard let responseObject = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
throw URLError(.badServerResponse)
}
completion(.success(responseObject))
} catch let parseError {
completion(.failure(parseError))
}
}
task.resume()
return task
}
This does all of the necessary wrapping of the method and parameters within a JSON-RPC request. Then, all you need to do to call that method is something like so:
submitLacunaRequest(module: "empire", method: "login", parameters: ["myuserid", "mypassword", "mykey"]) { result in
switch result {
case .failure(let error):
print("error = \(error)")
case .success(let value):
if let errorDictionary = value["error"] as? [String: Any] {
print("error logging in (bad userid/password?): \(errorDictionary)")
} else if let resultDictionary = value["result"] as? [String: Any] {
print("successfully logged in, refer to resultDictionary for details: \(resultDictionary)")
} else {
print("we should never get here")
print("responseObject = \(value)")
}
}
}
For a request that requires a dictionary, such as "create", just go ahead and supply the dictionary:
submitLacunaRequest(module:"empire", method: "create", parameters: [
"name" : "user",
"password" : "password",
"password1" : "password",
"captcha_guid" : "305...dd-....-....-....-e3706...73c0",
"captcha_solution" : "42",
"email" : "test#gmail.com"
]) { result in
switch result {
case .failure(let error):
print("error = \(error)")
case .success(let value):
print("responseObject = \(responseObject)")
}
}
Clearly, in these above, I am just doing minimal error handling, so you could beef this up, but your question was about issuing POST request, and hopefully the above illustrates how that is done.
For Swift 2 version, see previous revision of this answer.
As #jcaron points out, this post is full of bad edits. There's a lot of variables that have different names later in the function and so on. Not to mention you should NEVER post your api key in a SO question or anywhere on the internet for that matter.
To answer your question on to do a post request in Swift, unless you need incredibly granular control over the process, take a look at Alamofire (same guy that wrote AFNetworking).
A POST request is as simple as Alamofire.request(.POST, "http://someapiurl.com") You can also pass in a dictionary of body parameters if you so choose.

Resources