I want to send a POST request to my php 7 server which accepts data as application/x-www-form-urlencoded. The data I have is inside a Struct and I want to get every property of this struct as a parameter when I submit it.
This is the struct which handles my urlSession requests both GET and POST
XHR.swift
struct XHR {
enum Result<T> {
case success(T)
case failure(Error)
}
func urlSession<T>(method: String? = nil, file: String, data: Data? = nil, completionHandler: #escaping (Result<T>) -> Void) where T: Codable {
let file = file.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!
// Set up the URL request
guard let url = URL.init(string: file) else {
print("Error: cannot create URL")
return
}
var urlRequest = URLRequest(url: url)
if method == "POST" {
urlRequest.httpMethod = "POST";
urlRequest.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
urlRequest.httpBody = data
print(urlRequest.httpBody)
}
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// vs let session = URLSession.shared
// make the request
let task = session.dataTask(with: urlRequest, completionHandler: {
(data, response, error) in
DispatchQueue.main.async { // Correct
guard let responseData = data else {
print("Error: did not receive data")
return
}
let decoder = JSONDecoder()
print(String(data: responseData, encoding: .utf8))
do {
let todo = try decoder.decode(T.self, from: responseData)
completionHandler(.success(todo))
} catch {
print("error trying to convert data to JSON")
//print(error)
completionHandler(.failure(error))
}
}
})
task.resume()
}
}
This is the functions which sends a POST request to the server:
VideoViewModel.swift
struct User: Codable {
let username: String
let password: String
static func archive(w:User) -> Data {
var fw = w
return Data(bytes: &fw, count: MemoryLayout<User>.stride)
}
static func unarchive(d:Data) -> User {
guard d.count == MemoryLayout<User>.stride else {
fatalError("BOOM!")
}
var w:User?
d.withUnsafeBytes({(bytes: UnsafePointer<User>)->Void in
w = UnsafePointer<User>(bytes).pointee
})
return w!
}
}
enum Login {
case success(User)
case failure(Error)
}
func login(username: String, password: String, completionHandler: #escaping (Login) -> Void) {
let thing = User(username: username, password: password)
let dataThing = User.archive(w: thing)
xhr.urlSession(method: "POST", file: "https://kida.al/login_register/", data: dataThing) { (result: XHR.Result<User>) in
switch result {
case .failure(let error):
completionHandler(.failure(error))
case .success(let user):
//let convertedThing = User.unarchive(d: user)
completionHandler(.success(user))
}
}
}
And I call it like this:
videoViewModel.login(username: "rexhin", password: "bonbon") { (result: VideoViewModel.Login) in
switch result {
case .failure(let error):
print("error")
case .success(let user):
print(user)
}
}
From PHP I can see that a POST request is submitted successfully but when I try to get the username field by doing $_POST["username"] I get Undefined index:
Full code of the app can be seen here https://gitlab.com/rexhin/ios-kida.git
I used below code in swift 4
guard let url = URL(string: "http://192.168.88.129:81/authenticate") else {
return
}
let user1 = username.text!
let pass = passwordfield.text!
print(user1)
print(pass)
let data : Data = "username=\(user1)&password=\(pass)&grant_type=password".data(using: .utf8)!
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
print("one called")
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// vs let session = URLSession.shared
// make the request
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) in
if let error = error
{
print(error)
}
else if let response = response {
print("her in resposne")
}else if let data = data
{
print("here in data")
print(data)
}
DispatchQueue.main.async { // Correct
guard let responseData = data else {
print("Error: did not receive data")
return
}
let decoder = JSONDecoder()
print(String(data: responseData, encoding: .utf8))
do {
// let todo = try decoder.decode(T.self, from: responseData)
// NSAssertionHandler(.success(todo))
} catch {
print("error trying to convert data to JSON")
//print(error)
// NSAssertionHandler(.failure(error))
}
}
})
task.resume()
}
You are passing the result of User.archive(w: thing) as the data embedded in the request body, which may never work. Generally, your archive(w:) and unarchive(d:) would never generate any useful results and you should better remove them immediately.
If you want to pass parameters where x-www-form-urlencoded is needed, you need to create a URL-query-like string.
Try something like this:
func login(username: String, password: String, completionHandler: #escaping (Login) -> Void) {
let dataThing = "username=\(username)&password=\(password)".data(using: .utf8)
xhr.urlSession(method: "POST", file: "https://kida.al/login_register/", data: dataThing) { (result: XHR.Result<User>) in
//...
}
}
The example above is a little bit too simplified, that you may need to escape username and/or password before embedding it in a string, when they can contain some special characters. You can find many articles on the web about it.
Another way of doing this is as follows:
Add the URLEncodedFormEncoder.swift into your project. This is a custom URLEncodedFormEncoder from Alamofire / Vapor.
Conform your model to native Swift Encodable protocol, just as you do with JSON coding.
Encode the model just as you do during json encoding
// example
let requstModel = OpenIDCTokenRequest(
clientId: clientId,
clientSecret: clientSecret,
username: username,
password: password
)
guard let requestData: Data = try? URLEncodedFormEncoder().encode(requstModel) else {
return // handle encoding error
}
Quoting from this post
In PHP, a variable or array element which has never been set is
different from one whose value is null; attempting to access such an
unset value is a runtime error.
The Undefined index error occurs when you try to access an unset variable or an array element. You should use function isset inorder to safely access the username param from the POST body. Try the below code in your PHP file.
if (isset($_POST["username"]))
{
$user= $_POST["username"];
echo 'Your Username is ' . $user;
}
else
{
$user = null;
echo "No user name found";
}
Related
In a separate data file called data.swift I have this code
struct Response: Decodable {
var data: Data
}
struct Data: Decodable {
var search: search
}
struct search: Decodable {
var __Typename: String
var query: String
var searchResults: searchResults
}
...and so on and so forth. I then decode the data from a Rapid-Api like so
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 products = try? JSONSerialization.jsonObject(with: data!, options: .allowFragments)
if let water = products {
print("JSON: \n" + String(describing: water) + "\n")
}
}
})
How do I display the data elements in ProductList.swift it's a (view) file. The API works as expected and displays the JSON in the terminal. I am using XCODE 12.4 as I am not permitted to upgrade any further.
So, actually you want to receive the data in Model and show in view, either a label or image.
In your productSwift.List:
var response: Response?
Right now, you have to decode the data in the Model:
static func postApiCall<T: Decodable>(completion: #escaping (Result<T,Error>) -> Void) {
let url = URL(string: "Enter Your URL here")
let request = URLRequest(url: url!)
let dataTask = URLSession.shared.dataTask(with: request) { data, response , error in
guard let data = data else {
if error == nil {
completion(.failure(error as! Error))
}
return
}
do {
let decoder = JSONDecoder()
let json = try decoder.decode(T.self, from: data)
completion(.success(json))
} catch let error {
print(error.localizedDescription)
}
}
dataTask.resume()
}
}
Now in your ProductList.swift:
ServiceManage.postApiCall { (result : Result<Response,Error>) in
switch result {
case .success(let result):
print("result is \(result)")
self.response = response.data
self.yourLabel.text = response.data.search.query
case .failure(let failure):
print(failure)
}
}
and as Larme said, change your Struct "Data" name to something else.
I have code that calls an API to authenticate a user. The code is shown below:
guard let url = URL(string: "https://somewebsiteapiurl/login") else {
completion(.failure(NetworkError.badURL))
return
}
let body = LoginRequestBody(username: username, password: password)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONEncoder().encode(body)
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else {
completion(.failure(NetworkError.noData))
return
}
guard let loginResponse = try? JSONDecoder().decode(LoginResponse.self, from: data) else {
completion(.failure(NetworkError.decodingError))
return
}
guard let token = loginResponse.token else {
completion(.failure(AuthenticationError.invalidCredentials))
return
}
completion(.success(token))
}.resume()
}
Depending on the type of error I call the .failure with the respective Error case.
enum NetworkError: Error {
case badURL
case noData
case decodingError
}
enum AuthenticationError: Error {
case invalidCredentials
}
My first question is that is this a good way to do it. And also how would I handle these cases on the client side since sometimes the error comes from NetworkError enum and other times it is AuthenticationError.
This looks basically correct.
Personally, I would be inclined to dispatch completion handler back to the main queue, e.g.
func login(username: String, password: String, queue: DispatchQueue = .main, completion: #escaping (Result<String, Error>) -> Void) {
guard let url = URL(string: "https://somewebsiteapiurl/login") else {
queue.async { completion(.failure(NetworkError.badURL)) }
return
}
let body = LoginRequestBody(username: username, password: password)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try? JSONEncoder().encode(body)
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
queue.async { completion(.failure(error ?? NetworkError.noData)) }
return
}
guard let loginResponse = try? JSONDecoder().decode(LoginResponse.self, from: data) else {
queue.async { completion(.failure(NetworkError.decodingError)) }
return
}
guard let token = loginResponse.token else {
queue.async { completion(.failure(AuthenticationError.invalidCredentials)) }
return
}
queue.async { completion(.success(token)) }
}.resume()
}
This way the caller does not have to dispatch stuff back to the main queue itself (and if the caller wants to use a queue other than the main queue, it can override that).
I would also pass back the Error that URLSession provides as shown above. That way the caller can differentiate between different types of network problems. E.g. you might have a custom message for .notConnectedToInternet
For example:
login(username: username, password: password) { result in
switch result {
case .success(let token):
// do something with `token`
case .failure(AuthenticationError.invalidCredentials):
// present invalid credentials UI
case .failure(let error as URLError) where error.code == .notConnectedToInternet:
// present some nice 'looks like you are offline' message
case .failure(NetworkError.badURL), .failure(NetworkError.noData):
// present some generic network error message
// personally log these sorts of issues into Crashlytics, as this would suggest some programming bug, either in client app or in server, that DevOps or developers need to look into
default:
// present some generic network error message
}
}
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.
I'm trying to figure out how to store JSON data into a variable for later use. How do I store it, and is it possible to use the variable in another view controller, or do I have to do another request to fetch the data?
This is my code:
#IBAction func signinTapped(_ sender: UIButton) {
guard let url = URL(string: "http://XXXXXX/TestReqIOS.php") else {
return
}
let email = txtEmail.text!
let password = txtPassword.text!
let data : Data = "loginSubmit=1&email=\(email)&password=\(password)&grant_type=password".data(using: .utf8)!
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
print("Calling API")
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// vs let session = URLSession.shared
// make the request
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) in
if let error = error {
print("error")
}
else if let response = response {
print("response")
}
else if let data = data {
print(data)
}
DispatchQueue.main.async { // Correct
guard let responseData = data else {
print("Error: did not receive data")
return
}
print(String(data: responseData, encoding: String.Encoding.utf8) ?? "")
}
})
task.resume()
}
Which will return:
{
"id": "7",
"first_name": "John",
"last_name": "Doe",
"email": "JohnDoe#text.com",
"created": "2019-03-11",
"modified": "2019-03-10",
}
It would be better to use a struct, such as in your case:
struct Data: Codable {
let id: Int
let first_name: String
let last_name: String
let email: String
let created: Date
let modified: Date
}
Then you create a variable of that struct where you will store it:
var dataVariable = [Data]()
Then you can do your URL call like:
func getData(arr: Bool, completion: #escaping (Bool) -> ()) {
let urlJSON = "URL"
guard let url = URL(string: urlJSON) else { return }
URLSession.shared.dataTask(with: url) { (data, response, err) in
guard let data = data else { return }
do {
let getData = try JSONDecoder().decode([Data].self, from: data)
self.dataVariable = getData
} catch let jsonErr {
print("error serializing json: \(jsonErr)")
}
completion(arr)
}.resume()
}
Then you can access all of this from the dataVariable var. IF you do this in a Manager class you can access it from any ViewController.
To access:
let firstNameString = dataVariable[0].first_name
If there are not multiple trees of the same, then just make sure its:
let getData = try JSONDecoder().decode(Data.self, from: data)
Edit:
In your case put the above here:
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) in
if let error = error {
print("error")
}
else if let response = response {
print("response")
}
else if let data = data {
let getData = try JSONDecoder().decode([Data].self, from: data)
self.dataVariable = getData // <- Just decode here
print(data)
}
DispatchQueue.main.async { // Correct
guard let responseData = data else {
print("Error: did not receive data")
return
}
print(String(data: responseData, encoding: String.Encoding.utf8) ?? "")
}
})
task.resume()
It's my first experience with REST in iOS development with swift. I couldn't find any working or straight (simple) example for doing what i need here.
I have a login backend (https://myaddress.com/rest/login), where I need to pass 2 params: login and password. When I pass good values (user exists in database) I get 2 variables as a result: token (string) and firstLogin (bool). So when I get those values I know that login is successful and I can log in into my app.
So I am begging you for an example (just a simple function) of how to achieve that. If I get working code example I will know how to use it for other rest services in my app. I tried many solutions from tutorials I found, but any of them was working for me.. So to not waste my time searching I would like someone experienced to show me the way to achieve that.
I am not sure if Alamofire is so good to use, I know that swift 4 has it's own build neetwork services and to work with json. Any solution that works would be great.
Also, side question - if I would prefer to use Alamofire, do I need to use swiftyJSON also? Or it's just for parsing?
You can use URLSession if you don't like to import Alamofire in your Project to perform a simple task.
here are some method : GET, POST, DELETE METHODS and tutorial
GET METHOD
func makeGetCall() {
// Set up the URL request
let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"
guard let url = URL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
// set up the session
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
// make the request
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET on /todos/1")
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result as JSON, since that's what the API provides
do {
guard let todo = try JSONSerialization.jsonObject(with: responseData, options: [])
as? [String: Any] else {
print("error trying to convert data to JSON")
return
}
// now we have the todo
// let's just print it to prove we can access it
print("The todo is: " + todo.description)
// the todo object is a dictionary
// so we just access the title using the "title" key
// so check for a title and print it if we have one
guard let todoTitle = todo["title"] as? String else {
print("Could not get todo title from JSON")
return
}
print("The title is: " + todoTitle)
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
}
POST METHOD
func makePostCall() {
let todosEndpoint: String = "https://jsonplaceholder.typicode.com/todos"
guard let todosURL = URL(string: todosEndpoint) else {
print("Error: cannot create URL")
return
}
var todosUrlRequest = URLRequest(url: todosURL)
todosUrlRequest.httpMethod = "POST"
let newTodo: [String: Any] = ["title": "My First todo", "completed": false, "userId": 1]
let jsonTodo: Data
do {
jsonTodo = try JSONSerialization.data(withJSONObject: newTodo, options: [])
todosUrlRequest.httpBody = jsonTodo
} catch {
print("Error: cannot create JSON from todo")
return
}
let session = URLSession.shared
let task = session.dataTask(with: todosUrlRequest) {
(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
}
// parse the result as JSON, since that's what the API provides
do {
guard let receivedTodo = try JSONSerialization.jsonObject(with: responseData,
options: []) as? [String: Any] else {
print("Could not get JSON from responseData as dictionary")
return
}
print("The todo is: " + receivedTodo.description)
guard let todoID = receivedTodo["id"] as? Int else {
print("Could not get todoID as int from JSON")
return
}
print("The ID is: \(todoID)")
} catch {
print("error parsing response from POST on /todos")
return
}
}
task.resume()
}
DELETE METHOD
func makeDeleteCall() {
let firstTodoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"
var firstTodoUrlRequest = URLRequest(url: URL(string: firstTodoEndpoint)!)
firstTodoUrlRequest.httpMethod = "DELETE"
let session = URLSession.shared
let task = session.dataTask(with: firstTodoUrlRequest) {
(data, response, error) in
guard let _ = data else {
print("error calling DELETE on /todos/1")
return
}
print("DELETE ok")
}
task.resume()
}
Thanks #MAhipal Singh for you answer. I'll post here example with Alamafire that I used so it's all in one stack question. It's easier than I though, solutions I tried to use before were not working cause I had problems with pinning certificate about I forgot..
func loginRest(login:String, password:String, deviceId:String){
let urlStr = restServices.REST_MAIN_URL + restServices.REST_LOGIN
let params = ["login":login, "password":password, "deviceId":deviceId]
let paramsJson = try! JSONSerialization.data(withJSONObject: params)
var headers: HTTPHeaders = ["Content-Type": "application/json"]
Alamofire.request(urlStr, method: .post, parameters: params, encoding: JSONEncoding.default, headers: headers).responseJSON { (response) in
switch response.result {
case .success:
print("SUKCES with \(response)")
case .failure(let error):
print("ERROR with '\(error)")
}
}
If the post is proper the response is (console print):
SUKCES with SUCCESS: {
firstLogin = 1;
token = "dfkafjkfdsakfadsjfksjkfaadjfkjdfkjfskjfdkafjakfjakfjsafksjdafjy878328hjh";
}