Access variable in the same class in Swift - swift

Currently, I want to write a APIEndpoint class with contains all of my APIs. I have a variable baseURL and I want to use it for the remains. But when I tried to write it as below, it comes to error.
class APIEndpoint {
static let shared = APIEndpoint()
static let baseURL = "https://13.251.102.94:5001/api/"
static let login = URL(string: baseURL + "auth/customer/login")!
}
My function
func performLogin () {
var request = URLRequest(url: APIEndpoint.shared.login)
request.httpMethod = "POST"
let json: [String: String] = ["username": self.name, "password": self.password]
request.httpBody = try? JSONSerialization.data(withJSONObject: json)
Network.shared.session.dataTask(with: request) { (data, response, err) in
guard let data = data else { return }
print(JSON(data)["data"])
}.resume()
}
Error:
Static member 'login' cannot be used on instance of type 'APIEndpoint'
in line: var request = URLRequest(url: APIEndpoint.shared.login)

login and baseURL are static members of class APIEndpoint. You're already could use them as APIEndpoint.login and APIEndpoint.baseURL. In your case you should write
var request = URLRequest(url: APIEndpoint.login)

Related

Alamofire and TableView/CollectionView

I created a reusable Alamofire request which works smoothly. I am trying to get the data from the request Decode it(works fine too) and append it to one of my arrays to display in tableView/collectionView.
I am using MVVM and I append my data in viewModel(you can see below). The thing is I have tableView in my viewController and inside my tableView methods( viewForSupplementaryElementOfKind for instance) the 'stories'(from viewModel) are always empty.
In my viewDidLoad method I call getMainPageData(from viewModel) first and then create my tableView. I assure you the request itself is a success, the only problem is displaying the data.
Please keep in mind that the project has many API calls so I need a solution which will work in all cases when I have to deal with "lists". Thank you in advance
class NetworkManager {
let keychain = KeychainManager()
let base = "SomeBase"
let storageManager = StorageManager()
func setupRequest(path: Paths, method: RequestMethod, body: Encodable? = nil, params: [String: Any]? = nil, header: HeaderType, completion: #escaping((Result<Data,NetworkError>) -> Void)) {
var queries = ""
if let params = params {
queries = params.passingQuery()
}
let url = URL(string: base + path.rawValue + queries)
var request = URLRequest(url: url!)
request.httpMethod = method.rawValue
request.setValue(header.value[0].value, forHTTPHeaderField: "Content-Type")
if let userToken = keychain.getAccessToken(), userToken.count > 0 {
request.setValue("Bearer " + userToken, forHTTPHeaderField: "Authorization")
}
if let body = body {
if let jsonData = body.toJSONData() {
request.httpBody = jsonData
}
}
AF.request(request).validate().responseJSON { response in
if (200...299) ~= response.response?.statusCode ?? -1 {
self.handlingHeaders(response: response)
completion(.success(response.data!))
} else {
do {
if let data = response.data {
let json = try JSONDecoder().decode(ErrorResponse.self, from: data)
completion(.failure(.responseError(json.message)))
}
} catch {
completion(.failure(.serverError))
}
}
}
}
private func handlingHeaders(response: AFDataResponse<Any>) {
let headers = response.response?.headers
if let accessToken = headers?.dictionary["Authorization"] {
keychain.saveToken(token: accessToken)
}
}
}
extension Encodable {
func toJSONData() -> Data? { try? JSONEncoder().encode(self) }
}
var stories = [Story]()
func getMainPageData(completion: #escaping(Result<Void, NetworkError>) -> ()) {
networkManager.setupRequest(path: .mainPageData, method: .get, body: nil, params: nil, header: .application_json) { [self] result in
switch result {
case .success(let data):
do {
let homePageData = try JSONDecoder().decode(MainPageResponse.self, from: data)
stories.append(contentsOf: homePageData.model.stories)

Swift 'Required String parameter 'grant_type' is not present' error when trying to get an access token

So I'm trying to make an app in Swift for which I need to login to the OAuth to retrieve an access token. The API takes the parameters 'grant_type' and 'code'.
I've tried:
let params = ["grant_type":"authorization_code", "code":"123456789"] as Dictionary<String, String>
var request = URLRequest(url: URL(string: "https://myschool.zportal.nl/api/v3/oauth/token")!)
request.httpMethod = "POST"
request.httpBody = try? JSONSerialization.data(withJSONObject: params, options: [])
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in
do {
let json = try JSONSerialization.jsonObject(with: data!) as! Dictionary<String, AnyObject>
print(json)
} catch {
print("error")
}
})
task.resume()
This returns:
["response": {
data = (
);
details = "class org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter 'grant_type' is not present";
endRow = 0;
eventId = 216795;
message = "Internal problems.";
startRow = 0;
status = 500;
totalRows = 0;
}]
This is weird, because I did parse the 'grant_type' parameter, and as the correct value.
It should return something like:
{
"response": {
"status":200,
"message":"",
"startRow":0,
"endRow":27,
"totalRows":27,
"data":[
{
},
...
]
}
}
The standard message format requires a Form URL Encoded body, not a JSON one. Maybe adapt your code as in this Swift article.
Got it working:
#IBAction func sendButtonPressed(_ sender: UIButton) {
let urlString = "https://myschool.zportal.nl/api/v3/oauth/token"
performRequest(urlString: urlString)
}
func performRequest(urlString: String) {
if let url = URL(string: urlString) {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "grant_type=authorization_code&code=1234567890".data(using: .utf8)
let session = URLSession(configuration: .default)
let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in
//print(response!)
do {
let json = try JSONSerialization.jsonObject(with: data!) as! Dictionary<String, AnyObject>
print(json)
} catch {
print("error")
}
})
task.resume()
}
}
Not the nicest code but I'll polish it later.
At first I parsed JSON but it turned out I needed to parse a String and turn it into a Data type using: .data(using: .utf8)

Error while doing phone number verification using twilio in swift

I want to verify phone number by getting one time password. But I am getting some error. Please look into the code bellow and help me to resolve it. I am using Twilio for mobile verification. And Alamofire for API request. But the error I am getting like:-
Authentication Error - No credentials provided.
The data couldn’t be read because it isn’t in the correct format
My code is:-
Here is my model class: -
...struct SendVerificationCode: Codable {
let status: String?
let payee: String?
let dateUpdated: Date?
let sendCodeAttempts: [SendCodeAttempt]?
let accountSid, to: String?
let amount: Int?
let valid: Bool?
let lookup: Lookup?
let url: String?
let sid: String?
let dateCreated: Date?
let serviceSid, channel: String?
enum CodingKeys: String, CodingKey {
case status, payee
case dateUpdated = "date_updated"
case sendCodeAttempts = "send_code_attempts"
case accountSid = "account_sid"
case to, amount, valid, lookup, url, sid
case dateCreated = "date_created"
case serviceSid = "service_sid"
case channel
}
}
struct Lookup: Codable {
let carrier: Carrier?
}
struct Carrier: Codable {
let mobileCountryCode, type: String?
let errorCode: String?
let mobileNetworkCode, name: String?
enum CodingKeys: String, CodingKey {
case mobileCountryCode = "mobile_country_code"
case type
case errorCode = "error_code"
case mobileNetworkCode = "mobile_network_code"
case name
}
}
struct SendCodeAttempt: Codable {
let channel, time: String?
}...
Api Request:-
...func sendcode(mobileWithCode: String, completion: #escaping sendTwillioVerificationCodeCompletion) {
let url = URL(string: SEND_TWILIO_VERIFICATION_CODE)
var urlRequest = URLRequest(url: url!)
urlRequest.httpMethod = HTTPMethod.post.rawValue
urlRequest.addValue(userNameData, forHTTPHeaderField: "Username")
urlRequest.addValue(PasswordData, forHTTPHeaderField: "Password")
urlRequest.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
Alamofire.request(urlRequest).responseJSON { (response) in
if let error = response.result.error {
debugPrint(error.localizedDescription)
completion(nil)
return
}
guard let data = response.data else { return completion(nil)}
Common.sharedInstance().printRequestOutput(data: data)
let jsonDecoder = JSONDecoder()
do {
let clear = try jsonDecoder.decode(SendVerificationCode.self, from: data)
completion(clear)
} catch {
debugPrint(error.localizedDescription)
completion(nil)
}
}
}...
But i am getting error:-
{"code": 20003, "detail": "Your AccountSid or AuthToken was incorrect.", "message": "Authentication Error - No credentials provided", "more_info": "https://www.twilio.com/docs/errors/20003", "status": 401}
"The data couldn’t be read because it isn’t in the correct format."
Also i have tried the following code:-
import Foundation
semaphore = DispatchSemaphore (value: 0)
let parameters = "To=+919778882332&Channel=sms"
let postData = parameters.data(using: .utf8)
var request = URLRequest(url: URL(string: myUrl)!,timeoutInterval: Double.infinity)
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.addValue(requestData, forHTTPHeaderField: "Authorization")
request.httpMethod = "POST"
request.httpBody = postData
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
return
}
print(String(data: data, encoding: .utf8)!)
semaphore.signal()
}
task.resume()
semaphore.wait()
But i am getting error like
"Invalid parameter"
Twilio developer evangelist here.
It looks as though your code is trying to call the Twilio API directly from the device and that you weren't setting your Account SID or Auth Token in that.
The issue here is that you should not store or access your auth token from within your application. That would make your account sid and auth token vulnerable to be stolen and then used to abuse your account.
Instead, you should create a server side application that talks to the Twilio API and then call that from your application.
As Jamil pointed out, there is a blog post you can follow on performing phone verification in iOS with Twilio Verify and Swift and I recommend you go through that. It includes an example server side application to call the Twilio Verify API built in Python, but you could build your own too.
Here is sample code:
import UIKit
class ViewController: UIViewController {
static let path = Bundle.main.path(forResource: "Config", ofType: "plist")
static let config = NSDictionary(contentsOfFile: path!)
private static let baseURLString = config!["serverUrl"] as! String
#IBOutlet var countryCodeField: UITextField! = UITextField()
#IBOutlet var phoneNumberField: UITextField! = UITextField()
#IBAction func sendVerification() {
if let phoneNumber = phoneNumberField.text,
let countryCode = countryCodeField.text {
ViewController.sendVerificationCode(countryCode, phoneNumber)
}
}
static func sendVerificationCode(_ countryCode: String, _ phoneNumber: String) {
let parameters = [
"via": "sms",
"country_code": countryCode,
"phone_number": phoneNumber
]
let path = "start"
let method = "POST"
let urlPath = "\(baseURLString)/\(path)"
var components = URLComponents(string: urlPath)!
var queryItems = [URLQueryItem]()
for (key, value) in parameters {
let item = URLQueryItem(name: key, value: value)
queryItems.append(item)
}
components.queryItems = queryItems
let url = components.url!
var request = URLRequest(url: url)
request.httpMethod = method
let session: URLSession = {
let config = URLSessionConfiguration.default
return URLSession(configuration: config)
}()
let task = session.dataTask(with: request) {
(data, response, error) in
if let data = data {
do {
let jsonSerialized = try JSONSerialization.jsonObject(with: data, options: []) as? [String : Any]
print(jsonSerialized!)
} catch let error as NSError {
print(error.localizedDescription)
}
} else if let error = error {
print(error.localizedDescription)
}
}
task.resume()
}
}
For more please check this link: Link.

How to return an object from a method that contains a DataTask [duplicate]

This question already has answers here:
Swift: How do I return a value within an asynchronous urlsession function?
(3 answers)
Closed 5 years ago.
I am new on swift and trying to do something like that :
I have struct named Api Response :
struct ApiResponse {
var IsSuccess : Bool = false
var Message : String?
var ReturnedData : Data?
}
and have a func in another class named CommonHandler, that makes api call
public class CommonHandler {
public func CallApi(_ apiUrl : String , _ parameters : [String : Any] ) -> ApiResponse
{
var apiResponse = ApiResponse()
let url = URL(string: apiUrl)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try! JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
let task = URLSession.shared.dataTask(with: request , completionHandler : { data, response, error in
if let error = error {
// handle the transport error
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
// handle the server error
return
}
apiResponse.ReturnedData = data
apiResponse.IsSuccess = true
apiResponse.Message = "Succeed"
})
task.resume()
return apiResponse
}
}
I want to call this function in UIViewController like that :
var handler = CommonHandler()
let param :[String : String] = ["param":"param"]
let url = "url"
let response = handler.CallApi(url, param)
print(response.IsSuccess)
print(response.Message!)
I am aware that i can not use dataTask method like this. It's async.
But how can i do api call in a non-void func and return its response data ?
I parse ReturnedData json to struct then.
What is the best approach in this case ?
Thanks
Use an asynchronous completion handler
public func callApi(with url : String , parameters : [String : Any], completion: #escaping (ApiResponse?) -> () )
{
var apiResponse = ApiResponse()
let url = URL(string: apiUrl)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try! JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted)
let task = URLSession.shared.dataTask(with: request , completionHandler : { data, response, error in
if let error = error {
// handle the transport error
completion(nil)
return
}
guard let response = response as? HTTPURLResponse, response.statusCode == 200 else {
// handle the server error
completion(nil)
return
}
apiResponse.ReturnedData = data
apiResponse.IsSuccess = true
apiResponse.Message = "Succeed"
completion(apiResponse)
})
task.resume()
}
and call it:
var handler = CommonHandler()
let param = ["param":"param"]
let url = "url"
handler.callApi(with: url, parameters: param) { response in
if let response = response {
print(response.IsSuccess)
print(response.Message!)
}
}
Note:
Please conform to the naming convention that variable and function names start with lowercase letter and use parameter labels for better readability.

set body in NSMutableURLRequest doesn´t work

Header:
let header = ["Content-Type" : "application/x-www-form-urlencoded", "Authorization" : "Basic " + self.basicAuth];
Body:
var body : [String : AnyObject] = [:];
let body = ["grant_type" : "client_credentials", "scope" : "MessageSender"];
The Request and Serialization:
private func makeHTTPPostRequest(path: String, header: [String : String], body: [String: AnyObject], onCompletion: #escaping ServiceResponse) {
let request = NSMutableURLRequest(url: NSURL(string: path)! as URL)
// Set the method to POST
request.httpMethod = "POST"
do {
// Set the POST body for the request
let jsonBody = try JSONSerialization.data(withJSONObject: body, options: .prettyPrinted)
request.httpBody = jsonBody
let session = URLSession.shared
request.allHTTPHeaderFields = header;
let task = session.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in
if let httpResponse = response as? HTTPURLResponse {
if let jsonData = data {
let json:JSON = JSON(data: jsonData)
print(response)
print(json)
onCompletion(json,httpResponse, error as NSError?)
} else {
onCompletion(JSON.null,HTTPURLResponse.init(), error as NSError?)
}
}
})
task.resume()
} catch {
onCompletion(JSON.null,HTTPURLResponse.init(), nil)
}
}
}
When the request is done, it fires a 400 response with
{
"error_description" : "grant_type parameter is requiered field and it has to be non empty string.",
"error" : "invalid_request"
}
Obviously the body is not set correctly but I really don´t know why. I´m using this piece of code in other applications with no problem... .
The same request works like charm in Postman. The body in postman is set with type x-www-form-urlencoded.
Maybe the JSONSerialization is wrong ?
To send a POST request with Content-Type: application/x-www-form-urlencoded;, you need to create a URL query-like String and then convert it to a Data. Your code or any Swift Standard Library functions do not have the functionality. You may need to write it by yourself, or find a suitable third-party library. (Of course JSONSerialization is not suitable here, the String is not a JSON.)
With given a Dictionary<String, String>, you can do it like this:
var body: [String: String] = [:]
body = ["grant_type": "client_credentials", "scope": "MessageSender"]
(Simplified...)
request.httpBody = body.map{"\($0)=\($1)"}.joined(separator: "&").data(using: .utf8)
//`body.map{"\($0)=\($1)"}.joined(separator: "&")` -> grant_type=client_credentials&scope=MessageSender
(Strict... 4.10.22.6 URL-encoded form data)
extension CharacterSet {
static let wwwFormUrlencodedAllowed = CharacterSet(charactersIn: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._*" + "+")
}
extension String {
var wwwFormUrlencoded: String {
return self
.replacingOccurrences(of: " ", with: "+")
.addingPercentEncoding(withAllowedCharacters: .wwwFormUrlencodedAllowed)!
}
}
class HTTPBody {
static func wwwFormUrlencodedData(withDictionary dict: [String: String]) -> Data {
return body
.map{"\($0.wwwFormUrlencoded)=\($1.wwwFormUrlencoded)"}
.joined(separator: "&").data(using: .utf8)!
}
}
request.httpBody = HTTPBody.wwwFormUrlencodedData(withDictionary: body)
(Remember, not many servers interpret the received form data as strictly generated.)
One more, this is not a critical issue in this case, but you should better use Swift classes rather than NS-something:
typealias ServiceResponse = (JSON, HTTPURLResponse?, Error?)->Void
private func makeHTTPPostRequest(path: String, header: [String : String], body: [String: String], onCompletion: #escaping ServiceResponse) {
var request = URLRequest(url: URL(string: path)!)
// Set the method to POST
request.httpMethod = "POST"
// Set the POST body for the request (assuming your server likes strict form data)
request.httpBody = HTTPBody.wwwFormUrlencodedData(withDictionary: body)
let session = URLSession.shared
request.allHTTPHeaderFields = header;
let task = session.dataTask(with: request, completionHandler: {data, response, error -> Void in
if let httpResponse = response as? HTTPURLResponse {
if let jsonData = data {
let json:JSON = JSON(data: jsonData)
print(response)
print(json)
onCompletion(json, httpResponse, error)
} else {
onCompletion(JSON.null, httpResponse, error)
}
}
})
task.resume()
}