So I'm practising trying to make API calls with Swift. The code is as below:
struct Example: Codable {
let userID: String
let ID: String
let title: String
let completed: String
}
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
let decoder = JSONDecoder()
if let json: Example = try? decoder.decode(Example.self, from: data) {
completion(json)
}
}
}.resume()
}
}
getJson() { (json) in
print(json.ID)
}
However, I am unable to print out anything when getJson is called.
The example API I used is found here.
The tutorial that I used to help me write the code is found here.
Modified your variable name and data type exactly as your API response.
struct Example: Codable {
let userId: Int
let id: Int
let title: String
let completed: Bool
}
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
let decoder = JSONDecoder()
do {
let json: Example = try! decoder.decode(Example.self, from: data)
completion(json)
}catch let error {
print(error.localizedDescription)
}
}
}.resume()
}
}
getJson() { (json) in
print(json.id)
}
You can also use CodingKey and can change your response during the init period.
struct Example: Codable {
var userID: Int
var ID: Int
var title: String
var completed: Bool
enum CodingKeys: String, CodingKey {
case userID = "userId"
case ID = "id"
case title = "title"
case completed = "completed"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
userID = try values.decode(Int.self, forKey: .userID)
ID = try values.decode(Int.self, forKey: .ID)
title = try values.decode(String.self, forKey: .title)
completed = try values.decode(Bool.self, forKey: .completed)
title = "Title: \(title)"
}
}
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
do {
let json: Example = try JSONDecoder().decode(Example.self, from: data)
completion(json)
}catch let error {
print(error.localizedDescription)
}
}
}.resume()
}
}
getJson() { (json) in
print("ID: \(json.ID) \(json.title)")
}
import UIKit
class ApiManager: NSObject {
static func postApiRequest(url: String,parameters:[String: Any],completion: #escaping([String: Any])-> Void) {
let session = URLSession.shared
let request = NSMutableURLRequest(url: NSURL(string: url)! as URL)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
do{
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: JSONSerialization.WritingOptions())
let task = session.dataTask(with: request as URLRequest as URLRequest, completionHandler: {(data, response, error) in
if let response = response {
let nsHTTPResponse = response as! HTTPURLResponse
let statusCode = nsHTTPResponse.statusCode
print ("status code = \(statusCode)")
}
if let error = error {
print ("\(error)")
}
if let data = data {
do{
let jsonResponse = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions())
print ("data = \(jsonResponse)")
}catch _ {
print ("OOps not good JSON formatted response")
}
}
})
task.resume()
}catch _ {
print ("Oops something happened buddy")
}
}
}
I know that Codable = Decodable & Encodable but when calling json from xcode,
Codable was given as a struct, but an error saying
Argument type'login.Type' does not conform to expected type'Encodable' appears.
json code
struct login: Codable {
var userId: String?
var userPw: String?
class func LoginBoard(_ completeHandler: #escaping (login) -> Void) {
let loginboard: String = MAIN_URL + "/member/login"
guard let url = URL(string: loginboard) else {
print("Error: cannot create URL")
return
}
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")
urlRequest.httpBody = try? JSONEncoder().encode(login) // ERROR [Argument type 'login.Type' does not conform to expected type 'Encodable']
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) { (data, response, error) in
guard error == nil else {
print("error calling Post on /todos/1")
print(error!)
return
}
guard let responseData = data else {
print("Error: did not receive data")
return
}
do {
let decoder = JSONDecoder.init()
let LoginList = try decoder.decode(login.self, from: responseData)
completeHandler(LoginList)
}
catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
}
There is no error in try decoder.decode
but only in urlRequest.httpBody = try? JSONEncoder().encode(login) what is the problem?
You need to have something like this to set the values.
let loginvalues = login(userId: "john", userPw: "adfadfa")
urlRequest.httpBody = try? JSONEncoder().encode(loginvalues)
If you place this inside a play ground and run it you will see that you get the json data.
struct Login: Codable {
var userId: String?
var userPw: String?
}
let loginvalues = Login(userId: "john", userPw: "adfadfa")
let test = try? JSONEncoder().encode(loginvalues)
print(String(data: test!, encoding: .utf8)!)
Hi I'm new to Swift and I am trying to create a reusable generic Download Manager for URL Request that can be reused throughout my project in different View Controllers or reused within the same VC for a different URL Request calls. The problem that I have is how do I pass the Data Type from the Request into the Download Manager and then return the Downloaded Data back to the VC with the corresponding Data Type. I am able to pass the Data Type in a call to downloadRequest but I can't figure out how to pass the Data Type back to the VC via a delegate DownloadManagerDelegate. Any help would be greatly appreciate it!
Generic Download Manager:
protocol DownloadManagerDelegate {
func didUpdateData<T: Codable>(modelType: T.Type, downloadedData: T.Type)
}
struct DownloadManager {
var delegate: DownloadManagerDelegate?
func downloadRequest<T: Codable>(modelType: T.Type, parameters: [String: Any]) {
guard let url = URL(string: "https://www.someAPI...") else {return}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
guard let httpBodyWithParameters = try? JSONSerialization.data(withJSONObject: parameters, options: []) else
{
print("error")
return
}
request.httpBody = httpBodyWithParameters
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if error != nil {
print("error")
return
}
if let safeData = data {
if let downloadedData = parseDownloadedData(data: safeData) {
self.delegate?.didUpdateData(modelType: modelType, downloadedData: downloadedData)
}
}
}.resume()
func parseDownloadedData(data: Data) -> T?{
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(T.self, from: data)
return decodedData
} catch {
print(error)
return nil
}
}
}
Delegate in my VC:
override func viewDidLoad() {
super.viewDidLoad()
downloadManager.delegate = self
}
func didUpdateData(modelType: modelType,downloadedData:downloadedData){
DispatchQueue.main.async {
print(downloadedData)
}
}
To call download downloadRequest:
downloadManager.downloadrequest(modeType: Type1.self, parameters: parameters)
The Data Model is defined as a struct:
struct DataModel1: Codable {
let ItemID: String
}
Then in the same VC I call the same function downloadManager that will call a different API which should return data for a different Model Type (defined as Struct)
downloadManager.downloadRequest(modeType: Type2.self, parameters: parameters)
The Data Model is defined as a struct:
struct DataModel2: Codable {
let EmployeeeID: String
}
In the Swift times Protocol/Delegate smells a bit objective-c-ish.
I recommend a completion handler with the versatile Result type.
It returns the generic type non-optional on success and any error on failure.
The force unwrapping of data is safe because if error is nil then data has a value
struct DownloadManager {
func downloadRequest<T: Decodable>(modelType: T.Type, parameters: [String: Any], completion : #escaping (Result<T, Error>) -> Void) {
guard let url = URL(string: "https://www.someAPI...") else {return}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
do {
let httpBodyWithParameters = try JSONSerialization.data(withJSONObject: parameters)
request.httpBody = httpBodyWithParameters
let session = URLSession.shared
session.dataTask(with: request) { (data, response, error) in
if let error = error {
completion(.failure(error))
} else {
completion( Result { try JSONDecoder().decode(T.self, from: data!)})
}
}.resume()
} catch {
completion(.failure(error))
}
}
}
And use it
downloadManager.downloadrequest(modeType: Type1.self, parameters: parameters) { result in
switch result {
case .success(let data): print(data)
case .failure(let error): print(error)
}
}
I'm using Swift 4, I have a structure that I initialize with default values.
I made a function inside that is supposed to read a JSON and change those default values with what it gets but it doesn't seem to work.
Error: Closure cannot implicitly capture a mutating self parameter
Code:
struct Workspace: Decodable {
var guid: String
var name: String
private enum CodingKeys : String, CodingKey {
case guid = "guid"
case name = "name"
}
init(){
self.guid = "blbl"
self.name = "oops"
}
mutating func getUserWorkspace(base: String, completed: #escaping () -> ()){
let url = URL(string: "some url")!
var request = URLRequest(url: url)
request.addValue("Basic \(base)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request){ (data, response, error) in
if error == nil {
do {
let res: Workspace = try JSONDecoder().decode(Workspace.self, from: data!)
self.guid = res.guid //Error here
self.name = res.name //Error here
DispatchQueue.main.async {
completed()
}
}catch {
print ("JSON error")
}
}
}.resume()
}
I changed let to var, but I guess there's still something I don't understand..
//Try this one and let me know
struct Workspace: Decodable {
static var shared = Workspace()
var guid: String
var name: String
private enum CodingKeys : String, CodingKey {
case guid = "guid"
case name = "name"
}
init(){
self.guid = "blbl"
self.name = "oops"
}
mutating func getUserWorkspace(base: String, completed: #escaping () -> ()){
let url = URL(string: "some url")!
var request = URLRequest(url: url)
request.addValue("Basic \(base)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request){ (data, response, error) in
if error == nil {
do {
let res: Workspace = try JSONDecoder().decode(Workspace.self, from: data!)
Workspace.shared.guid = res.guid //Error here
Workspace.shared.name = res.name //Error here
DispatchQueue.main.async {
completed()
}
}catch {
print ("JSON error")
}
}
}.resume()
}
}
I would suggest you to use class instead of struct. Anyway if you like to use your code, then capture the self inside your mutation method like below:
mutating func getUserWorkspace(base: String, completed: #escaping () -> ()){
let url = URL(string: "some url")!
var request = URLRequest(url: url)
var myself = self
request.addValue("Basic \(base)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request){ (data, response, error) in
if error == nil {
do {
let res: Workspace = try JSONDecoder().decode(Workspace.self, from: data!)
myself.guid = res.guid //Error here
myself.name = res.name //Error here
DispatchQueue.main.async {
completed()
}
}catch {
print ("JSON error")
}
}
}.resume()
}
I actually found the problem.. and it was pretty stupid.. I just had to change the "completed" part of the function, like this:
mutating func getUserWorkspace(base: String, completed: #escaping (_ arr:[Workspace]?) -> ()){
let url = URL(string: "SOME URL")!
var request = URLRequest(url: url)
request.addValue("Basic \(base)", forHTTPHeaderField: "Authorization")
request.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: request){ (data, response, error) in
if error == nil {
do {
let rep = try JSONDecoder().decode([Workspace].self, from: data!)
DispatchQueue.main.async {
completed(rep)
}
}catch {
print(error)
}
}
}.resume()
}
I wonder if it's possible to directly send an array (not wrapped in a dictionary) in a POST request. Apparently the parameters parameter should get a map of: [String: AnyObject]?
But I want to be able to send the following example json:
[
"06786984572365",
"06644857247565",
"06649998782227"
]
You can just encode the JSON with NSJSONSerialization and then build the NSURLRequest yourself. For example, in Swift 3:
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let values = ["06786984572365", "06644857247565", "06649998782227"]
request.httpBody = try! JSONSerialization.data(withJSONObject: values)
AF.request(request) // Or `Alamofire.request(request)` in prior versions of Alamofire
.responseJSON { response in
switch response.result {
case .failure(let error):
print(error)
if let data = response.data, let responseString = String(data: data, encoding: .utf8) {
print(responseString)
}
case .success(let responseObject):
print(responseObject)
}
}
For Swift 2, see previous revision of this answer.
For swift 3 and Alamofire 4 I use the following ParametersEncoding and Array extension:
import Foundation
import Alamofire
private let arrayParametersKey = "arrayParametersKey"
/// Extenstion that allows an array be sent as a request parameters
extension Array {
/// Convert the receiver array to a `Parameters` object.
func asParameters() -> Parameters {
return [arrayParametersKey: self]
}
}
/// Convert the parameters into a json array, and it is added as the request body.
/// The array must be sent as parameters using its `asParameters` method.
public struct ArrayEncoding: ParameterEncoding {
/// The options for writing the parameters as JSON data.
public let options: JSONSerialization.WritingOptions
/// Creates a new instance of the encoding using the given options
///
/// - parameter options: The options used to encode the json. Default is `[]`
///
/// - returns: The new instance
public init(options: JSONSerialization.WritingOptions = []) {
self.options = options
}
public func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = try urlRequest.asURLRequest()
guard let parameters = parameters,
let array = parameters[arrayParametersKey] else {
return urlRequest
}
do {
let data = try JSONSerialization.data(withJSONObject: array, options: options)
if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest.httpBody = data
} catch {
throw AFError.parameterEncodingFailed(reason: .jsonEncodingFailed(error: error))
}
return urlRequest
}
}
Basically, it converts the array to a Dictionary in order to be accepted as Parameters argument, and then it takes back the array from the dictionary, convert it to JSON Data and adds it as the request body.
Once you have it, you can create request this way:
let values = ["06786984572365", "06644857247565", "06649998782227"]
Alamofire.request(url,
method: .post,
parameters: values.asParameters(),
encoding: ArrayEncoding())
Here is an example of encoding an Array of type Thing to JSON, using a router, and Ogra to do the JSON encoding:
import Foundation
import Alamofire
import Orga
class Thing {
...
}
enum Router: URLRequestConvertible {
static let baseURLString = "http://www.example.com"
case UploadThings([Thing])
private var method: Alamofire.Method {
switch self {
case .UploadThings:
return .POST
}
}
private var path: String {
switch self {
case .UploadThings:
return "upload/things"
}
}
var URLRequest: NSMutableURLRequest {
let r = NSMutableURLRequest(URL: NSURL(string: Router.baseURLString)!.URLByAppendingPathComponent(path))
r.HTTPMethod = method.rawValue
switch self {
case .UploadThings(let things):
let custom: (URLRequestConvertible, [String:AnyObject]?) -> (NSMutableURLRequest, NSError?) = {
(convertible, parameters) in
var mutableRequest = convertible.URLRequest.copy() as! NSMutableURLRequest
do {
let jsonObject = things.encode().JSONObject()
let data = try NSJSONSerialization.dataWithJSONObject(jsonObject, options: NSJSONWritingOptions.PrettyPrinted)
mutableRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
mutableRequest.HTTPBody = data
return (mutableRequest, nil)
} catch let error as NSError {
return (mutableRequest, error)
}
}
return ParameterEncoding.Custom(custom).encode(r, parameters: nil).0
default:
return r
}
}
}
Swift 2.0
This code below post object array.This code is tested on swift 2.0
func POST(RequestURL: String,postData:[AnyObject]?,successHandler: (String) -> (),failureHandler: (String) -> ()) -> () {
print("POST : \(RequestURL)")
let request = NSMutableURLRequest(URL: NSURL(string:RequestURL)!)
request.HTTPMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
var error: NSError?
do {
request.HTTPBody = try NSJSONSerialization.dataWithJSONObject(postData!, options:[])
} catch {
print("JSON serialization failed: \(error)")
}
Alamofire.request(request)
.responseString{ response in
switch response.result {
case .Success:
print(response.response?.statusCode)
print(response.description)
if response.response?.statusCode == 200 {
successHandler(response.result.value!)
}else{
failureHandler("\(response.description)")
}
case .Failure(let error):
failureHandler("\(error)")
}
}
}
#manueGE 's answer is right. I have a similar approach according to alamofire github's instruction:
`
struct JSONDocumentArrayEncoding: ParameterEncoding {
private let array: [Any]
init(array:[Any]) {
self.array = array
}
func encode(_ urlRequest: URLRequestConvertible, with parameters: Parameters?) throws -> URLRequest {
var urlRequest = urlRequest.urlRequest
let data = try JSONSerialization.data(withJSONObject: array, options: [])
if urlRequest!.value(forHTTPHeaderField: "Content-Type") == nil {
urlRequest!.setValue("application/json", forHTTPHeaderField: "Content-Type")
}
urlRequest!.httpBody = data
return urlRequest!
}
}
`
Then call this by customize a request instead of using the default one with parameter. Basically discard the parameter, since it is a dictionary.
let headers = getHeaders()
var urlRequest = URLRequest(url: URL(string: (ServerURL + Api))!)
urlRequest.httpMethod = "post"
urlRequest.allHTTPHeaderFields = headers
let jsonArrayencoding = JSONDocumentArrayEncoding(array: documents)
let jsonAryEncodedRequest = try? jsonArrayencoding.encode(urlRequest, with: nil)
request = customAlamofireManager.request(jsonAryEncodedRequest!)
request?.validate{request, response, data in
return .success
}
.responseJSON { /*[unowned self] */(response) -> Void in
...
}
Also, the way to handle error in data is very helpful.
let url = try Router.baseURL.asURL()
// Make Request
var urlRequest = URLRequest(url: url.appendingPathComponent(path))
urlRequest.httpMethod = "post"
// let dictArray: [[String: Any]] = []
urlRequest = try! JSONEncoding.default.encode(urlRequest, withJSONObject: dictArray)
Something I do in my project to upload a JSON array
func placeOrderApi(getUserId:String,getDateId:String,getTimeID:String,getAddressId:String,getCoupon:String)
{
let data = try! JSONSerialization.data(withJSONObject: self.arrOfServices, options: [])
let jsonBatch : String = String(data: data, encoding: .utf8)!
//try! JSONSerialization.data(withJSONObject: values)
let params = [
"user_id":getUserId,
"time_id":getTimeID,
"date_id":getDateId,
"address_id":getAddressId,
"services":jsonBatch,
"payment_mode":paymentVia,
"coupon":getCoupon
] as [String : Any]
print(params)
self.objHudShow()
Alamofire.request(BaseViewController.API_URL + "place_order", method: .post, parameters: params, encoding: JSONEncoding.default)
.responseJSON { response in
debugPrint(response)
switch response.result {
case .success (let data):
print(data)
self.objHudHide()
if response.result.value != nil
{
let json : JSON = JSON(response.result.value!)
if json["status"] == true
{
}
else
{
self.view.makeToast(NSLocalizedString(json["msg"].string ?? "", comment: ""), duration: 3.0, position: .bottom)
}
}
break
case .failure:
self.objHudHide()
print("Error in upload:)")
break
}
}
}
There are 2 approach to send send JSON content as parameter.
You can send json as string and your web service will parse it on server.
d["completionDetail"] = "[{"YearOfCompletion":"14/03/2017","Completed":true}]"
You can pass each value within your json (YearOfCompletion and Completed) in form of sequential array. And your web service will insert that data in same sequence. Syntax for this will look a like
d["YearOfCompletion[0]"] = "1998"
d["YearOfCompletion[1]"] = "1997"
d["YearOfCompletion[2]"] = "1996"
d["Completed[0]"] = "true"
d["Completed[1]"] = "false"
d["Completed[2]"] = "true"
I have been using following web service call function with dictionary, to trigger Alamofire request Swift3.0.
func wsDataRequest(url:String, parameters:Dictionary<String, Any>) {
debugPrint("Request:", url, parameters as NSDictionary, separator: "\n")
//check for internete collection, if not availabale, don;t move forword
if Rechability.connectedToNetwork() == false {SVProgressHUD.showError(withStatus: NSLocalizedString("No Network available! Please check your connection and try again later.", comment: "")); return}
//
self.request = Alamofire.request(url, method: .post, parameters: parameters)
if let request = self.request as? DataRequest {
request.responseString { response in
var serializedData : Any? = nil
var message = NSLocalizedString("Success!", comment: "")//MUST BE CHANGED TO RELEVANT RESPONSES
//check content availability and produce serializable response
if response.result.isSuccess == true {
do {
serializedData = try JSONSerialization.jsonObject(with: response.data!, options: JSONSerialization.ReadingOptions.allowFragments)
//print(serializedData as! NSDictionary)
//debugPrint(message, "Response Dictionary:", serializedData ?? "Data could not be serialized", separator: "\n")
}catch{
message = NSLocalizedString("Webservice Response error!", comment: "")
var string = String.init(data: response.data!, encoding: .utf8) as String!
//TO check when html coms as prefix of JSON, this is hack mush be fixed on web end.
do {
if let index = string?.characters.index(of: "{") {
if let s = string?.substring(from: index) {
if let data = s.data(using: String.Encoding.utf8) {
serializedData = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.allowFragments)
debugPrint(message, "Courtesy SUME:", serializedData ?? "Data could not be serialized", separator: "\n")
}
}
}
}catch{debugPrint(message, error.localizedDescription, "Respone String:", string ?? "No respone value.", separator: "\n")}
//let index: Int = text.distance(from: text.startIndex, to: range.lowerBound)
debugPrint(message, error.localizedDescription, "Respone String:", string ?? "No respone value.", separator: "\n")
}
//call finised response in all cases
self.delegate?.finished(succes: response.result.isSuccess, and: serializedData, message: message)
}else{
if self.retryCounter < 1 {//this happens really frequntly so in that case this fn being called again as a retry
self.wsDataRequest(url: url, parameters: parameters)
}else{
message = response.error?.localizedDescription ?? (NSLocalizedString("No network", comment: "")+"!")
SVProgressHUD.showError(withStatus: message);//this will show errror and hide Hud
debugPrint(message)
//call finised response in all cases
self.delay(2.0, closure: {self.delegate?.finished(succes: response.result.isSuccess, and: serializedData, message:message)})
}
self.retryCounter += 1
}
}
}
}
I think based on Alamofire documentation you can write the code as following:
let values = ["06786984572365", "06644857247565", "06649998782227"]
Alamofire.request(.POST, url, parameters: values, encoding:.JSON)
.authenticate(user: userid, password: password)
.responseJSON { (request, response, responseObject, error) in
// do whatever you want here
if responseObject == nil {
println(error)
} else {
println(responseObject)
}
}