Swift communicating with multiple webservices - swift

I currently need to use a web service to do some tasks which are, logging in and receiving a list of information.
After logging in successfully the web service will return 'response' information : {"LoginID":"1","Password":"","Role":"pol","LoginType":"Indevidual","UserID":"6110895204062016","UserRoleID":"20202020202020","RoleID":"999674512042008","PartyId":"1063081525122008","PartyFunctionId":"123123","BranchCode":"10","RoleCode":"123123","Status":{"isError":false,"ErCode":null,"Error":null}}
which is needed to be sent to another web service to get a list of information.
Currently using the login button to call the webserivce to be able to login.
How do I call another webservice using the information from the first webservice?
Code for a better idea:
#IBAction func GetPolicyListButton(_ sender: Any) {
//I will need the information from the second web service to display after clicking this button.. how?
}
#IBAction func LoginButton(_ sender: Any) {
let postString = "cpr=\(usernameField.text!)&password=\(passwordField.text!)"
let url = URL(string:"http://login")!
let postData:Data = postString.data(using: String.Encoding.utf8, allowLossyConversion: false)!
let postLength:String = String(postData.count) as String
var request:URLRequest = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = postData
request.setValue(postLength as String, forHTTPHeaderField: "Content-Length")
request.setValue("application/json", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print("error=\(error)")
return
}
let httpStatus = response as? HTTPURLResponse
print("statusCode should be 200, but is \(httpStatus!.statusCode)")
print("response = \(response!)")
print(postString)
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(responseString!)")
let start = responseString!.index(responseString!.startIndex, offsetBy: 75)
let end = responseString!.index(responseString!.endIndex, offsetBy: -9)
let range = start..<end
let jsonStr = responseString!.substring(with: range)
print(jsonStr)
let data1 = jsonStr.data(using: .utf8)!
_ = try? JSONSerialization.jsonObject(with: data1) as? [String: Any]
let persondata = try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
let personInfodata = persondata as? [String : Any]
_ = personInfodata?[""] as? [String : Any]
if (responseString?.contains("1001"))!{
DispatchQueue.main.async {
print("incorrect - try again")
let alert = UIAlertController(title: "Try Again", message: "Username or Password Incorrect", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
else{
DispatchQueue.main.async {
print("correct good")
let storyboard = UIStoryboard(name: "Maintest", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "correctone")
self.present(controller, animated: true, completion: nil)
}
}
}
task.resume()
}

You are experiencing the complexity of not working in MVC. While writing an application if you do not properly use MVC the complexity and unnecessary duplication of code can get out of hand and you lose oversight.
A style for example to use is, is to create a LoginModel and a ItemsModel for lack of a better name. Both will be making web requests so be sure to create a class that handles a generic web request or implement a framework like Alamofire (which has some great examples for authentication and automatic retrying of requests based on Tokens etc)
Now in your ViewController seperate all the handling of your data to a View-Independant LoginClass like this:
#IBAction func LoginButton(_ sender: UIButton) {
guard let username = usernameField.text else { print("no username") ; return }
guard let password = passwordField.text else { print("no password") ; return }
self.loginModel.login(username: username, password: password) { [weak self] success in
if success {
let dataModel = dataModel(credentials: credentialStyle)
dataModel.loadItems { items : [Item]? in
// Dispatch items to main queue
}
}
}
}
Now in your loginModel you handle the login and in a completely separate model you handle the dataModel which you instantiate with the credentials you received from the loginModel. Off course this is a rough example and using Alamofire you can use a Session Manager for example which will take care of the authentication (see the URL of 'automatic retrying of requests', scroll down a little bit and there is an example of authentication.) removing the need of instantiating your dataModel with credentials put this is purely to demonstrate how to split up your code to handle these requests.

Related

Authentication error/ purple warning when trying to segue after validating data from API

I have created a POST request which validates if the username and password are correct through a StatusCode: 0 that comes from the response of the POST request if the data are correct, At the signInViewController class I have created the button signInSegueToDashboard which when pressed must validate the data and if the data are valid then the user will be logged in without any problem.
The button sender at signInViewController:
#IBAction func signInSegueToDashboard(_ sender: Any) {
APICallerPOST.shared.signInToAccount(username: emailTextField.text!, password: passwordTextField.text!) { (result, error) in
if let result = result {
if result.StatusCode == 0 {
guard let mainTabBarController = self.storyboard?.instantiateViewController(withIdentifier: "mainTabBarController") else {
return
}
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) {_ in
mainTabBarController.modalPresentationStyle = .custom
self.present(mainTabBarController, animated: true, completion: nil)
}
}else if result.StatusCode == 5 {
print("error: \(error!.localizedDescription)")
}
}
}
}
When i press the button after typing the correct data it just does nothing and just shows a purple warning that is saying to put it on Main thread, When i did put on main thread the segue part then it doesn't validate the data at all instead it just logs you in without any validation.
the POST request from APICallerPOST class:
func signInToAccount(username: String, password: String, completion: #escaping (SignInResponse?, Error?) -> Void) {
//declare parameter as a dictionary which contains string as key and value combination.
let parameters = ["User": username, "Password": password]
//create the url with NSURL
let url = URL(string: "https://censoredurl/Signin")!
//create the session object
let session = URLSession.shared
//now create the Request object using the url object
var request = URLRequest(url: url)
request.httpMethod = "POST" //set http method as POST
do {
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: .prettyPrinted) // pass dictionary to data object and set it as request body
} catch let error {
print(error.localizedDescription)
completion(nil, error)
}
//HTTP Headers
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.addValue("application/json; charset=utf-8", forHTTPHeaderField: "Accept")
//create dataTask using the session object to send data to the server
let task = session.dataTask(with: request, completionHandler: { data, response, error in
guard error == nil else {
completion(nil, error)
return
}
guard let data = data else {
completion(nil, NSError(domain: "dataNilError", code: -100001, userInfo: nil))
return
}
do {
//create json object from data
let decoder = JSONDecoder()
guard let json = try? decoder.decode(SignInResponse.self, from: data) else {
completion(nil, NSError(domain: "invalidJSONTypeError", code: -100009, userInfo: nil))
return
}
print(json)
completion(json, nil)
} catch let error {
print(error.localizedDescription)
completion(nil, error)
}
})
task.resume()
}
Confused a lot.
The dataTask is asynchronous, and so is the code it runs in the completion handler. However all updates to UI need to be performed on the main thread, and so the parts of the completion handler that update the UI need to be pushed back onto the main thread.
In your case you could do it like this:
if result.StatusCode == 0 {
DispatchQueue.main.async {
guard let mainTabBarController = self.storyboard?.instantiateViewController(withIdentifier: "mainTabBarController")
else {
return
}
Timer.scheduledTimer(withTimeInterval: 1, repeats: false) {_ in
mainTabBarController.modalPresentationStyle = .custom
self.present(mainTabBarController, animated: true, completion: nil)
}
}
// ...
However it seems you are trying to use the Timer to delay presentation of the viewController, and there is a better way of doing this than using the Timer. You can use a delayed execution with DispatchQueue's asyncAfter(deadline:qos:flags:execute:) method:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// do your UI work
}

How can I parse JSON from a rest API in swift

I'm modifying code from 'Hacking with swift' Project 7 to take a JSON file using an API and placing it in a table view
I'm at a bit of loss of what to do next, tried moving around the call to the parse function and using the commented out code
override func viewDidLoad() {
super.viewDidLoad()
let username = "UserName"
let password = "Password"
let loginData = String(format: "%#:%#", username,
password).data(using: String.Encoding.utf8)!
let base64LoginData = loginData.base64EncodedString()
let url = URL(string: "......")!
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Basic \(base64LoginData)", forHTTPHeaderField:
"Authorization")
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 {
parse(json: data)
print("status code = \(httpStatus.statusCode)")
}
}
task.resume()
}
// if let url = URL(string: urlstring){
// if let data = try? Data(contentsOf: url){
// parse(json: data)
// return
// }
// }
// showError()
//}
func parse(json: Data) {
let decoder = JSONDecoder()
if let jsonPetitions = try? decoder.decode(Petitions.self, from:
json) {
petitions = jsonPetitions.results
tableView.reloadData()
}
}
I receive a status code of '200' so I know the API call works fine.
The issue seems to be with calling the parse function I get the
following error "Call to method 'parse' in closure requires explicit
'self.' to make capture semantics explicit"
1- The error means to add self here
self.parse(json: data)
2- You should reload the table in main thread as callback of URLSession.shared.dataTask runs in a background thread to avoid un-expected results/crashes
DispatchQueue.main.async {
self.tableView.reloadData()
}

I am doing a post request where I want to type in a question and with the post request get the most common answer

I have done my Post-request but I am unsure about how to make it possible to send a full question and to get the most common answers back to my app.
I am in such a big need of this code in my program so would love to get some examples on how to make it work
Have tried to right the question into the parameters with a "+" instead of space which resulted into nothing.
#IBAction func GetAnswer(_ sender: Any) {
let myUrl = URL(string: "http://www.google.com/search?q=");
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"
let postString = questionAsked;
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil
{
print("error=\(String(describing: error))")
return
}
print("response = \(String(describing: response))")
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let answer = parseJSON[" Answer "] as? String
self.AnswerView.text = ("Anwer: \(String(describing: answer))")
}
} catch {
print(error)
}
}
task.resume()
}
You do not use google.com/search, please check the api documentation
Paste following in Playground, should give a good start
struct Constants {
static let apiKey = "YOUR_API_KEY"
static let bundleId = "YOUR_IOS_APP_BUNDLE_ID"
static let searchEngineId = "YOUR_SEARCH_ENGINE_ID"
}
func googleSearch(term: String, callback:#escaping ([(title: String, url: String)]?) -> Void) {
let urlString = String(format: "https://www.googleapis.com/customsearch/v1?q=%#&cx=%#&key=%#", term, Constants.searchEngineId, Constants.apiKey)
let encodedUrl = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
guard let url = URL(string: encodedUrl ?? urlString) else {
print("invalid url \(urlString)")
return
}
let request = NSMutableURLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10)
request.httpMethod = "GET"
request.setValue(Constants.bundleId, forHTTPHeaderField: "X-Ios-Bundle-Identifier")
let session = URLSession.shared
let datatask = session.dataTask(with: request as URLRequest) { (data, response, error) in
guard
error == nil,
let data = data,
let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String : Any]
else {
// error handing here
callback(nil)
return
}
guard let items = json["items"] as? [[String : Any]], items.count > 0 else {
print("no results")
return
}
callback(items.map { ($0["title"] as! String, $0["formattedUrl"] as! String) })
}
datatask.resume()
}
Usage
googleSearch(term: "George Bush") { results in
print(results ?? [])
}
Create a new search engine using following url
https://cse.google.com/cse/create/new
If you would like search entire web, use following steps
edit your engine using https://cse.google.com/cse/setup/basic?cx=SEARCH_ENGINE_ID
remove any pages listed under Sites to search
turn on Search the entire web

Get user authentication before next UI View will appear

I am working on user authentication process but i stuck in the moment when reciving data from rest with token. Whenever i create the new task it does not enter on the first time into the function but after creating it skipping doing smth else which is showing a next hooked up UIViewController to segue.
My rest service with post method hashing user password, creating json, URL request and at the end creating URLSession. How could i wait for finish of this task ? To not let to do anything else before it is not complited ?
EDIT
I've added OpeartionQueue to liquidate nil's from next view.
func postLogin(name:String, pass:String, completion: #escaping (Bool) -> () ) {
let md5Data = self.MD5(string:pass)
let hashPass = md5Data!.map { String(format: "%02hhx", $0) }.joined()
let json: [String: Any] = ["username": name,
"passwordHash": hashPass ]
let jsonData = try? JSONSerialization.data(withJSONObject: json)
// create post request
let url = URL(string: LOGIN_URL)!
var request = URLRequest(url: url)
request.httpMethod = "POST"
// insert json data to the request
request.httpBody = jsonData
request.setValue("application/json;charest=utf-8", forHTTPHeaderField: "Content-Type")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "No data")
return
}
let responseJSON = try? JSONSerialization.jsonObject(with: data, options: [])
if let responseJSON = responseJSON as? [String: Any] {
print(responseJSON)
let message:String = responseJSON["message"] as! String
if !(message.range(of: "ERROR") != nil){
SessionMenager.Instance.token = message
completion(true)
}
} else{
print(error.debugDescription)
}
}
task.resume()
}
Then simply in my LoginViewController action with button :
#IBAction func LoginButton(_ sender: Any) {
let username = usernameTextField.text
let password = passwordTextField.text
if username == "" {
AlertWindow(title: "Username", message: "Wrong username")
} else if password == "" {
AlertWindow(title: "Password", message: "Wrong password")
} else {
let usernameToUpper = username!.uppercased()
RestService.Instance.postLogin(name: usernameToUpper, pass: password!, completion: { sth in
if sth {
OperationQueue.main.addOperation {
[weak self] in
self?.performSegue(withIdentifier: "mapSegue", sender: self)
}
} else {
return
}
})
}
}
The segue was hooked up into LoginButton which took me instantly to the next page. I've changed it into hooking up all view controllerr.
Thanks!
Because your segue is hooked up into LoginButton, it will automatically show the next viewController once you press the button.
Just hoop up the segue to the whole viewController and it should work.

URL request using Swift

I have access the "dictionary" moviedb for
example : https://www.themoviedb.org/search/remote/multi?query=exterminador%20do%20futuro&language=en
How can i catch only the film's name and poster from this page to my project in Swift ?
It's answer :)
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
reload()
}
private func reload() {
let requestUrl = "https://www.themoviedb.org/search/remote/multi?query=exterminador%20do%20futuro&language=en"
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let request = NSURLRequest(URL: NSURL(string: requestUrl)!)
let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
if let error = error {
println("###### error ######")
}
else {
if let JSON = NSJSONSerialization.JSONObjectWithData(data,
options: .AllowFragments,
error: nil) as? [NSDictionary] {
for movie in JSON {
let name = movie["name"] as! String
let posterPath = movie["poster_path"] as! String
println(name) // "Terminator Genisys"
println(posterPath) // "/5JU9ytZJyR3zmClGmVm9q4Geqbd.jpg"
}
}
}
})
task.resume()
}
}
You need to include your api key along with the request. I'd just try something like this to see if it works or not. If it does, then you can go about using the api key in a different way to make it more secure. I wouldn't bother as it's not an api with much sensitive functionality.
let query = "Terminator+second"
let url = NSURL(string: "http://api.themoviedb.org/3/search/keyword?api_key=YOURAPIKEY&query=\(query)&language=‌​en")!
let request = NSMutableURLRequest(URL: url)
request.addValue("application/json", forHTTPHeaderField: "Accept")
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { data, response, error in
if let response = response, data = data {
print(response)
//DO THIS
print(String(data: data, encoding: NSUTF8StringEncoding))
//OR THIS
if let o = NSJSONSerialization.JSONObjectWithData(data, options: nil, error:nil) as? NSDictionary {
println(dict)
} else {
println("Could not read JSON dictionary")
}
} else {
print(error)
}
}
task.resume()
The response you'll get will have the full list of properties. You need the poster_path and title (or original_title) property of the returned item.