I am trying to transition into a new segue after I confirm that I get a the session ID from the Udacity API. However, I cannot get any code to run after this. The session ID and all the information will print, but nothing will complete after. Is there something I am doing wrong in the function? I tried performing the UIUpdates then just tried printing "Hello" but that has not worked either. Below is some code and also my GitHub profile.
https://github.com/SteveBurgos95/UdacityMapProject
#IBAction func loginButton(_ sender: Any) {
UdacityClient.sharedInstance().loginWithUsername(emailTextField.text!, password: passwordTextField.text!){ (success, error) in
performUIUpdatesOnMain {
if success {
print("Hello")
} else {
print("Hey")
}
}
}
private func completeLogin() {
emailTextField.text = ""
let controller = storyboard!.instantiateViewController(withIdentifier: "TableMapViewController") as! UINavigationController
present(controller, animated: true, completion: nil)
It seems to me like you're never calling your completionhandler in UdacityClient.swift when the dataTask finishes (both in case of error and success)?
This should be something like this:
let task = session.dataTask(with: request as URLRequest) { data, response, error in
// Make sure there is no error, or else
guard error == nil else { // Handle error…
completionHandler(false, error?.localizedDescription)
/*
func sendError(_ error: String) {
print(error)
let userInfo = [NSLocalizedDescriptionKey : error]
completionHandlerForGET(nil, NSError(domain: "taskForGETMethod", code: 1, userInfo: userInfo))
*/
return
}
completionHandler(true, nil)
}
After looking through the code the UI is not getting updated because your have not implemented the sucess block in UdacityClient.swift. The sucess block/closure escapes. So you won't receive a callback in
UdacityClient.sharedInstance().loginWithUsername(emailTextField.text!, password: passwordTextField.text!){
Change the function like below
func loginWithUsername(_ username: String, password: String, completionHandler: #escaping (_ success: Bool, _ errorMessage: String?) -> Void ) {
let request = NSMutableURLRequest(url: URL(string: "https://www.udacity.com/api/session")!)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Accept")
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = "{\"udacity\": {\"username\": \"\(username)\", \"password\": \"\(password)\"}}".data(using: String.Encoding.utf8)
let session = URLSession.shared
let task = session.dataTask(with: request as URLRequest) { data, response, error in
if error != nil
// Handle error…
/*
func sendError(_ error: String) {
print(error)
let userInfo = [NSLocalizedDescriptionKey : error]
completionHandlerForGET(nil, NSError(domain: "taskForGETMethod", code: 1, userInfo: userInfo))
*/
return
}
completionHandler(true,nil)
let range = Range(5..<data!.count)
let newData = data?.subdata(in: range) /* subset response data! */
print(NSString(data: newData!, encoding: String.Encoding.utf8.rawValue)!)
}
task.resume()
}
Related
I am making an app where I am using API to create login interface.
In my LoginViewModel class I have following login function
import Foundation
class LoginViewModel: ObservableObject {
#Published var loginuser : Login?
// func login(username: String, password: String, callback : #escaping (Bool,LoginData,String) -> Void) {
func login(with params: [String: Any], completionHandler: #escaping (Bool,LoginData,String) -> Void) {
// let parameters: [String: Any] = [
// "email": username,
// "password": password
// ]
var semaphore = DispatchSemaphore (value: 0)
guard let url = URL(string: "https://xxxxxxxxx/v1//user/login")else {return}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
//request.addValue("token", forHTTPHeaderField: "Authorization")
do {
request.httpBody = try JSONSerialization.data(withJSONObject: params)
} catch let error {
print(error.localizedDescription)
}
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
semaphore.signal()
return
}
print(String(data: data, encoding: .utf8)!)
}//urlsession
task.resume()
}//login func
}
And in my view I have button whose action has following code
Button(action: {
let params = ["email": userName,
"password": password]
viewModel.login(with: params) { success, data, message in
if success==true {
UserDefaultsStore.isUserLoggedIn = true
// UserDefaultsStore.loginuser = login
showsAlert4 = true
print ("cat1")
}
else
{
print ("cat2")
}
}
}, label: {
Image("btn_arrow")
While I can confirm the API is being read fine as console puts out data from this line in func login
print(String(data: data, encoding: .utf8)!)
What I can not get to work is show "cat1" or "cat2" message in case of successful or unsuccessful login attempt. In fact this piece of code just never executes
if success==true {
UserDefaultsStore.isUserLoggedIn = true
// UserDefaultsStore.loginuser = login
showsAlert4 = true
print ("cat1")
}
else
{
print ("cat2")
}
Any idea where I am going wrong in this? Any help will be appreciated. Thanks
I have tried changing completionHandler: to callback: in func login but that changes nothing.
As someone in the comments mentioned, you are not calling your completionHandler at all. You could also drop the semaphore as it is not needed. Or maybe I am missing the point of the semaphore and you can elaborate on that.
import Foundation
class LoginViewModel: ObservableObject {
#Published var loginuser : Login?
func login(with params: [String: Any], completionHandler: #escaping (Bool,LoginData,String) -> Void) {
var semaphore = DispatchSemaphore (value: 0)
guard let url = URL(string: "https://xxxxxxxxx/v1//user/login")else {return}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
do {
request.httpBody = try JSONSerialization.data(withJSONObject: params)
} catch let error {
print(error.localizedDescription)
}
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else {
print(String(describing: error))
semaphore.signal()
completionHandler(false, nil, "Error") -> ON FAILURE
return
}
print(String(data: data, encoding: .utf8)!)
completionHandler(true, data, "") -> ON SUCCESS
}
task.resume()
}
In my project i have all POST method JSON URL's, so i need to create one separate function where i need to pass url and parameters and i need to handle response as well .. Now I want to use that function in all viewcontrollers with differnt urls and parameters... but i am unable to create separate function for JSON
I am writing same code for all viewcontrollrs with different urls and jsonpostParameters like below
func loginService(){
let url = URL(string: "https://e/api/login")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
let jsonpostParameters = LoginData(jsonrpc: "2.0", params: (PostLogin(email: nameTf.text!, password: passwordTf.text!, device_id: "2")))
do {
let jsonBody = try JSONEncoder().encode(jsonpostParameters)
request.httpBody = jsonBody
} catch {
print("Error while encoding parameter: \(error)")
}
let session = URLSession.shared
let task = session.dataTask(with: request) { [self] (data, response, error) in
guard let data = data else {return}
do{
let jsonModel = try JSONDecoder().decode(Employees.self, from: data)
print("new model \(jsonModel.result)")
DispatchQueue.main.sync{
if jsonModel.error != nil{
let controller = UIAlertController(title: "Alert", message: "Your email is not verified", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: nil)
let cancel = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
controller.addAction(ok)
controller.addAction(cancel)
self.present(controller, animated: true, completion: nil)
}
else{
let vc = UIStoryboard.init(name: "Main", bundle: Bundle.main).instantiateViewController(withIdentifier: "ProfileViewController") as? ProfileViewController
vc?.userData = jsonModel
vc?.tokenKey = jsonModel.result!.token
self.navigationController?.pushViewController(vc!, animated: true)
}
}
print("the error is \(error)")
}catch{ print("Error while decoding: \(error.localizedDescription)") }
}
task.resume()
}
how to write all code in one function with different urls and different parameters and handle the response from JSON, to call that function in all view controllers, pls do help with code
Here check this protocol
protocol Networking {
func fetch(_ url: String, _ params:[String : Any], completion: #escaping (Data?, Error?) -> Void)
}
extension Networking {
func fetch(_ url: String, _ params:[String : Any], completion: #escaping (Data?, Error?) -> Void) {
//create the url with URL
let url = URL(string: url)!
//now create the URLRequest object using the url object
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST" //set http method as POST
do {
urlRequest.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted) // pass dictionary to nsdata object and set it as request body
} catch let error {
print(error.localizedDescription)
}
// Set this in header if required
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.addValue("application/json", forHTTPHeaderField: "Accept")
URLSession.shared.dataTask(with: urlRequest) { data, response, error in
if let error = error {
completion(nil, error)
return
}
guard let data = data else {
preconditionFailure("No error was received but we also don't have data...")
}
completion(data, nil)
}.resume()
}
}
And use this as below example
class SampleViewController: UIViewController, Networking {
override func viewDidLoad() {
super.viewDidLoad()
let url = "https://e/api/login"
let jsonpostParameters: [String: Any] = [:]
self.fetch(url, jsonpostParameters) { (data: Data?, error: Error?) in
guard let dt = data else { return }
// convert data to JSON
print(dt)
}
}
}
I am making an iOS app using SwiftUI that requires login. When the create account button is pressed, the action triggers a function in my NetworkManager class that sends the inputed email and password as a post request and receives the appropriate data back for authentication. It then uses the received data to determine whether the credentials are valid
My issue is that it runs the code that verifies the inputed credentials against the API response before the response is actually received. Consequently, the result is the same each time.
Button(action: {
self.networkManager.signUp(email: self.inputEmail, password: self.inputPassword)
// These if statements run before the above line is executed
if self.networkManager.signUpResponse.code == nil {
// SUCCESSFUL REGISTRATION
ProgressHUD.showSuccess("Account Successfully Created!")
UserDefaults.standard.set(true, forKey: "LoggedIn")
self.showingWelcomePage = false
}
if self.networkManager.signUpResponse.code == 201 {
// REGISTRATION FAILED
ProgressHUD.showError("This account already exists", interaction: false)
}
}) {
Text("Create Account")
.font(.headline)
}
I have tried using DispatchQueue.main.async() or creating my own thread, however nothing seems to work. I need to find a way to pause the main thread in order to wait for this line of code to execute before proceeding without using DispatchQueue.main.sync() as this results in deadlock and program crash.
Here is the code for the function that makes the post request to the API
class NetworkManager: ObservableObject {
#Published var signUpResponse = AccountResults()
func signUp(email: String, password: String) {
if let url = URL(string: SignUpAPI) {
let session = URLSession.shared
let bodyData = ["school": "1",
"email": email,
"password": password]
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = try! JSONSerialization.data(withJSONObject: bodyData)
let task = session.dataTask(with: request) { (data, response, error) in
if error == nil {
let decoder = JSONDecoder()
if let safeData = data {
do {
let results = try decoder.decode(AccountResults.self, from: safeData)
DispatchQueue.main.async {
self.signUpResponse = results
}
} catch {
print(error)
}
}
}
}
task.resume()
}
}
}
Thanks in advance!
Try it:
func signUp(email: String, password: String, completion: #escaping((Error?, YourResponse?) -> Void)) {
if let url = URL(string: SignUpAPI) {
let session = URLSession.shared
let bodyData = ["school": "1",
"email": email,
"password": password]
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = try! JSONSerialization.data(withJSONObject: bodyData)
let task = session.dataTask(with: request) { (data, response, error) in
if error == nil {
let decoder = JSONDecoder()
if let safeData = data {
do {
let results = try decoder.decode(AccountResults.self, from: safeData)
DispatchQueue.main.async {
self.signUpResponse = results
completion(nil, results)
}
} catch {
print(error)
completion(error, nil)
}
}
}
}
task.resume()
}
}
}
use escaping in your function, I think will get exactly the point server response data or get errors too.
my english is so bad.
Well I am new to Swift and I don't know much of completion handler. I want to get a request from an API and parse the JSON response so I can get the token. But what's happening with my code is that whenever I call the getAuthentication function my UI freezes and waiting for the data to get. Here is the code for getAuthentication
func getAuthentication(username: String, password: String){
let semaphore = dispatch_semaphore_create(0);
let baseURL = "Some URL here"
let url = NSURL(string: baseURL)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = "{\n \"username\": \"\(username)\",\n \"password\": \"\(password)\"\n}".dataUsingEncoding(NSUTF8StringEncoding);
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
//parse the data to get the user
self.id = swiftyJSON["id"].intValue
self.token = swiftyJSON["meta"]["token"].stringValue
} else {
print("There was an error")
}
dispatch_semaphore_signal(semaphore);
}
task.resume()
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
then, I am calling this method in my LoginViewController. Someone says that I am using a Synchronous request thats why my UI freezes, but I have really no idea on how to change it to Async and wait for the data to be downloaded. Can someone help me with this? Any help will much be appreciated.
Firstly, remove dispatch_semaphore related code from your function.
func getAuthentication(username: String, password: String){
let baseURL = "Some URL here"
let url = NSURL(string: baseURL)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = "{\n \"username\": \"\(username)\",\n \"password\": \"\(password)\"\n}".dataUsingEncoding(NSUTF8StringEncoding);
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
//parse the data to get the user
self.id = swiftyJSON["id"].intValue
self.token = swiftyJSON["meta"]["token"].stringValue
} else {
print("There was an error")
}
}
task.resume()
}
In the above code, the function dataTaskWithRequest itself is an asynchronus function. So, you don't need to call the function getAuthentication in a background thread.
For adding the completion handler,
func getAuthentication(username: String, password: String, completion:((sucess: Bool) -> Void)){
let baseURL = "Some URL here"
let url = NSURL(string: baseURL)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = "{\n \"username\": \"\(username)\",\n \"password\": \"\(password)\"\n}".dataUsingEncoding(NSUTF8StringEncoding);
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
var successVal: Bool = true
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
self.id = swiftyJSON["id"].intValue
self.token = swiftyJSON["meta"]["token"].stringValue
} else {
print("There was an error")
successVal = false
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion(successVal)
})
}
task.resume()
}
It can be called as follows:
self.getAuthentication("user", password: "password", completion: {(success) -> Void in
})
You may pass an escaping closure argument to getAuthentication method.
func getAuthentication(username: String, password: String, completion: (JSON) -> ()){
...
// create a request in the same way
...
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
completion(swiftyJSON)
} else {
print("There was an error")
}
}
task.resume()
}
And call getAuthentication in LoginViewController like this:
getAuthentication(username, password) { (json) -> in
//Do whatever you want with the json result
dispatch_async(dispatch_get_main_queue()) {
// Do UI updates
}
}
Another way to go is calling getAuthentication in a background thread in your LoginViewController to avoid blocking the main thread (i.e. UI thread).
//In LoginViewController
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
getAuthentication(username, password)
dispatch_async(dispatch_get_main_queue()) {
// UI updates
}
}
I have working json fetching codes with GET methods but when i convert it to POST with adding;
request.httpMethod = "POST"
gives me ;
Cannot assign to property: 'httpMethod' is a get-only property Error
My codes under below how can fix it ? How can i convert it to POST
#discardableResult
open func getLoginMethod(_ method: String, parameters: String, completionHandler: #escaping (_ login: [Login]?, _ error: Error?) -> Void) -> URLSessionTask! {
let session = URLSession.shared
guard let url = NSURL(string: parameters) else {
completionHandler(nil, myErrors.InvalidUrlError)
return nil
}
let request = NSURLRequest(url: url as URL)
let task = session.dataTask(with: request as URLRequest) { data, response, error in
if error != nil {
completionHandler(nil, myErrors.getError)
} else {
do {
let login = try self.getLogin(jsonData: data! as NSData)
completionHandler(login, nil)
} catch {
completionHandler(nil, error)
}
}
}
task.resume()
return task
}
Also my calling codes under below for get method my variables inside parameters , for POST method which changes i need to do ?
AWLoader.show(blurStyle: .dark, shape: .Circle)
DispatchQueue.global(qos: .background).async {
myApp.sharedInstance().getLoginMethod("", parameters: "http://bla.com/Post?Phone=\(self.phone.text)") {login, error in
if error != nil {
DispatchQueue.main.async {
// self.showAlert()
print(error!)
AWLoader.hide()
}
} else {
DispatchQueue.main.async {
self.getlogin = login!
// self.collectionView.reloadData()
// AWLoader.hide()
// self.loginButton.alpha = 0
print(self.getlogin)
AWLoader.hide()
}
}
}
}
Thanks
You need to use NSMutableURLRequest to be able to modify httpMethod. Using NSURLRequest won't do.
Example:
let request = NSMutableURLRequest(url: url)
request.httpMethod = "POST"
But even better, you should be using URLRequest struct:
var request = URLRequest(url: url)
request.httpMethod = "POST"
Your refactored code with additional improvements:
#discardableResult
open func getLoginMethod(_ method: String, parameters: String,
session: URLSession = .shared,
completionHandler: #escaping (_ login: [Login]?, _ error: Error?) -> Void) -> URLSessionTask! {
guard let url = URL(string: parameters) else {
completionHandler(nil, myErrors.InvalidUrlError)
return nil
}
var request = URLRequest(url: url)
request.httpMethod = method
let task = session.dataTask(with: request) { data, _, _ in
guard let responseData = data else {
completionHandler(nil, myErrors.getError)
return
}
do {
let login = try self.getLogin(jsonData: data as NSData)
completionHandler(login, nil)
} catch {
completionHandler(nil, error)
}
}
task.resume()
return task
}
EDIT:
To answer your second part of the question. It depends on what kind of data format your API is expecting - whether it should be multipart/form-data or json format, it all depends.
But in general, assuming that you are using URLRequest struct that I've mentioned, you should just convert it to Data using your serializer and use it like this:
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = <your parameters converted to Data>
Usually, your API will expect proper Content-Type header added to your URLRequest:
request.setValue("application/json", forHTTPHeaderField: "Content-Type")