I've been trying to figure out why my JSON data did not get appended into my global variables. I know that it is because it takes time for my functions to get fetch and process the JSON data. So I've tried to using a completion block to check to see if there's anything in it. However, I still got nothing inside. I am wondering where am I doing it worng?
Here's the code:
#IBAction func dismissButton(_ sender: UIButton) {
yelpAPIRequest(completion: {
print("WE are done")
print(self.businessName)
})
}
func yelpAPIRequest(completion:#escaping ()->()){
let coordinate = YLPCoordinate(latitude: userLatitude , longitude: userLongitude)
let query = YLPQuery(coordinate: coordinate)
query.term = "dessert"
query.limit = 5
query.radiusFilter = 16094
YLPClient.authorize(withAppId: clientId, secret: clientSecret).flatMap { client in
client.search(withQuery: query)
}.onSuccess { search in
if search.businesses.isEmpty == false {
let topBusiness = search.businesses
for i in topBusiness{
print(i.identifier)
self.retrieveBusinessFromAPI(id: i.identifier)
}
completion()
} else {
print("No businesses found")
}
}.onFailure { error in
print("Search errored: \(error)")
}
}
func retrieveBusinessFromAPI(id: String){
let url = URL(string: "https://api.yelp.com/v3/businesses/\(id)")
var request: URLRequest = URLRequest(url: url!)
request.setValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
request.httpMethod = "GET"
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
let businessData = JSON(data)
self.businessName.append(businessData["name"].string)
self.businessYelpURL.append(businessData["url"].string)
self.businessImageURL.append(businessData["image_url"].string)
self.businessIsOpenNow.append(businessData["hours"][0]["is_open_now"].bool)
self.businessPrice.append(businessData["price"].string)
self.businessRating.append(businessData["rating"].int)
self.businessAddress.append([businessData["location"]["display_address"].string])
})
task.resume()
}
EDIT: Yes ive looked at other posts about this same topic but the makority of them all pointed towards the completion: and I've done that but my data is still not saved
Related
I am trying to parse a nested Json in SwiftUI for the last couple of days and I have no idea how to move forward.
At this point, I suspect that the trouble is a parameter received within the Json named "data" which might cause a confusion between the param value in struct "VTResponse" and the data param that URLSession.shared.dataTask is getting.
Here's the code at this point:
import UIKit
struct VTResponse: Decodable {
let data: [VT]
}
struct VT: Decodable {
var id: String
}
let token = "<TOKEN>"
let XDOMAIN = "<XDOMAIN>"
guard let url = URL(string: "https://www.lalalla.com/subdomains") else {
fatalError("Invalid URL")
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("x-apikey: \(token)", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data, error == nil else { return }
let result = try? JSONDecoder().decode(VTResponse.self, from: data)
if let result = result {
result.data.forEach {
print($0.id)
}
}
else {
print("Error")
}
}.resume()
Assuming that I define a token and domain for the query, for example, lookup all of the subdomains of "giphy.com", the Json response:
Json Response - Pastebin
As you can see in the Json response, the subdomains parameter ("id") is under a dictionary, under an array("data"). My guess is the code is trying to assign data to the variable:
guard let data = data, error == nil else { return }
But this is just a guess. And even if so, how could I solve this?
Anyways, I'm getting the following output:
Error
I'm trying to get the following output:
pingback.giphy.com
media3.giphy.com
api.giphy.com
developers.giphy.com
media.giphy.com
x-qa.giphy.com
media1.giphy.com
x.giphy.com
media4.giphy.com
media0.giphy.com
Any ideas?
try this using an x-apikey header for your token:
func fetchData(token: String, XDOMAIN: String, completion: #escaping (VTResponse) -> Void) {
guard let url = URL(string: "https://www.virustotal.com/api/v3/domains/\(XDOMAIN)/subdomains") else {
fatalError("Invalid URL")
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("\(token)", forHTTPHeaderField: "x-apikey") // <-- here
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else { return } // todo return some error msg
do {
let results = try JSONDecoder().decode(VTResponse.self, from: data)
return completion(results)
} catch {
print(error) // <-- here important
}
}.resume()
}
And use it like this:
fetchData(token: "xxx", XDOMAIN: "www") { results in
results.data.forEach {
print("---> id: \($0.id)")
}
}
EDIT-1: to get an array of [String], that is, of id, use this function:
func ListFromSubDomains(token: String, XDOMAIN: String, completion: #escaping ([String]) -> Void) {
fetchData(token: token, XDOMAIN: XDOMAIN) { results in
completion(results.data.map{ $0.id })
}
}
and use it like this:
var IPList: [String] = []
ListFromSubDomains(token: "xxx", XDOMAIN: "www") { ids in
IPList = ids
print("---> ids: \(ids)")
}
I'm trying to retrieve some data from an API, but I got an error: "The given data was not valid JSON ", Status code: 401
I think that is an authentication problem. How can I set the auth credentials to make the GET request?
This is the code for retrieving the data from the JSON.
func loadData()
{
guard let url = URL(string: getUrl) else { return }
URLSession.shared.dataTask(with: url) { data, res, err in
do {
if let data = data {
let result = try JSONDecoder().decode([ItemsModel].self, from: data)
DispatchQueue.main.async {
self.items = result
}
} else {
print(" No Data ")
}
} catch( let error)
{
print(res)
print(String(describing: error))
}
}.resume()
}
This is the code for the view :
struct GetView: View {
#ObservedObject var viewModel = ViewModel()
var body: some View {
NavigationView {
VStack {
List(viewModel.items, id: \.id) { item in
Text(item.year)
}
} .onAppear(perform: {
viewModel.loadData()
})
.navigationTitle("Data")
}
}
}
To handle authentication you must implement a delegate for your URLSession:
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
completionHandler(.performDefaultHandling, challenge.proposedCredential)
}
However, your 401 error may be due to your code not sending a valid GET request to the server. You probably want to decode the 'res' value to determine the status code:
if let response = res as? HTTPURLResponse {
if response.statusCode != 200 {
// data may be JSON encoded but you should get some for
// statusCode == 401
}
}
Without knowing the kind of service you are connecting to it is hard to speculate if you need a GET or a POST. The URL you use may require a query parameter.
I found the solution. This is the code for Basic Auth :
func loadData() {
//Credentials
let username = ""
let password = ""
let loginString = "\(username):\(password)"
let loginData = loginString.data(using: String.Encoding.utf8)!
let base64LoginString = loginData.base64EncodedString()
//Request
guard let url = URL(string: getUrl) else {return}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
//Setup Session
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
do {
if let data = data {
print(String(data: data, encoding: .utf8)!)
let result = try JSONDecoder().decode([ItemsModel].self, from: data)
DispatchQueue.main.async {
self.items = result
}
}
else {
print(" No Data ")
}
} catch( let error)
{
print(String(describing: error))
}
}
task.resume()
}
The JSON data is
[
{"id":0,"temperature":77,"humidity":0.22,"timeCaptured":"2020-09-25T19:33:27.9733333"},
{"id":0,"temperature":77,"humidity":0.22,"timeCaptured":"2020-09-25T20:38:53.3"},
{"id":0,"temperature":85,"humidity":0.25,"timeCaptured":"2020-09-25T20:38:53.3"},
{"id":0,"temperature":88,"humidity":0.22,"timeCaptured":"2020-09-28T15:30:00"},
// ...
]
My structs look like this
struct TemperatureDataModel: Codable{
let id: Int?
let temperature: Double?
let humidty: Double?
let timeCaptured: String?
}
My function looks like this
func getTemperData(){
//Create the URLs
let temperatureDataUrl = URL(string: "https://weatherstationapi.azurewebsites.net/api/TemperatureSensor/GetData")
// let WindDataUrl = URL(string: "https://weatherstationapi.azurewebsites.net/api/WindData/GetAllData")
guard let requestURLTemp = temperatureDataUrl else { fatalError() }
//Create URL request
var request = URLRequest(url: requestURLTemp)
//Specifiy HTTP Method to use
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiaWxpci50YWlyaUB0dHUuZWR1IiwiaHR0cDovL3NjaGVtYXMueG1sc29hcC5vcmcvd3MvMjAwNS8wNS9pZGVudGl0eS9jbGFpbXMvbmFtZWlkZW50aWZpZXIiOiI4MjEzYzhhMy1iODgxLTQ4NmUtOGUyMC1mZmNlMDlmNGY0ZjgiLCJuYmYiOiIxNjAyNTI2NDI1IiwiZXhwIjoiMTYwNTExODQyNSJ9.t1qnYyXLpRRJ3YQfhgLrylBqL_pdKOnKVMgOfG9IuVc", forHTTPHeaderField: "Authorization")
//Send HTTP Request
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data else {return}
print(data)
//Use parseJSON to convert data
let TemperatureData = parseJSON(data: data)
// for singleValue in TemperatureData {
// print(singleValue.temperautre)
// }
//read list
guard let TemperatureDataModel = TemperatureData else {return}
print("Temperature is : \(TemperatureDataModel.temperature)")
// Check if error took place
if let error = error {
print("Error took place \(error)")
return
}
//Read HTTP Response Status Code
// if let data = data, let dataString = String(data: data, encoding: .utf8) {
// print("Response data string:\n \(dataString)")
// }
}
task.resume()
}
and then my JSON decoder function looks like this
func parseJSON(data: Data) -> TemperatureDataModel? {
var returnValue: TemperatureDataModel?
do {
let returnValue = try JSONDecoder().decode([TemperatureDataModel].self, from: data)
} catch {
print("Error took place \(error).")
}
return returnValue
}
I've looked at 6+ stack overflow posts now and still cannot figure It out. Ive tried putting my model in [] for an array, moving where the function is called, changing the jsondecoder function and more and nothing works.
I think you have to give a format to the date before you parse the data
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
Your issue there is that you are creating another returnValue that is not being returned. You need also change the return type to [TemperatureDataModel]
func parseJSON(data: Data) -> [TemperatureDataModel]? {
do {
return try JSONDecoder().decode([TemperatureDataModel].self, from: data)
} catch {
print("Error took place \(error).")
return nil
}
}
Im trying to access the variable pic after the request is made but its in a closure, thats why print(pic) doesn't work.
How would someone go about accessing this?
guard let url = URL(string: "myurl") else{ return }
var pic = ""
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
print(data)
do {
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
pic = parseJSON["picture"] as! String
print(json!)
}
} catch {
print(error)
}
}
}.resume()
print(pic)
}
Assuming pic is an image you'll be loading into a UIImageView:
You can add an Activity Indicator to your ImageView. Then when you call your function to download the pic simply add:
guard let url = URL(string: "myurl") else{ return }
activityIndicator.isHidden = false
activityIndicator.startAnimating()
The user will know a download is occurring. When complete,
DispatchQueue.main.async {
activityIndicator.isHidden = true
activityIndicator.stopAnimating()
myImageView.image = UIImage(named: "pic")
}
}.resume
Dispatching on the main que will update the UI immediately.
I have my controller like this -
def create
if (#user = User.find_by_email(params[:email])) && #user.valid_password?(params[:password])
render json: #user.as_json(only: [:email,:authentication_token]),status: :created
else
render json:('Unauthorized Access')
end
end
When I use Postman to make this request, I choose Body, and form data and adds in the email and password. And this WORKS
How to use swift to do the same? This is what I have tried
let url = URL(string: "http://localhost:3000/api/v1/user_serialized/")
let config = URLSessionConfiguration.default
let request = NSMutableURLRequest(url: url!)
request.httpMethod = "POST"
let bodyData = "email=Test#test.com&password=Test1234"
request.httpBody = bodyData.data(using: String.Encoding.utf8);
let session = URLSession(configuration: config)
let task = session.dataTask(with: url! as URL, completionHandler: {(data, response, error) in
let json = JSON(data:data!)
debugPrint(json)
})
task.resume()
I have made a Custom HTTP class where we can sent url, parameter and we will get Data from API. Below is the class.
import Foundation
//HTTP Methods
enum HttpMethod : String {
case GET
case POST
case DELETE
case PUT
}
class HttpClientApi: NSObject{
//TODO: remove app transport security arbitary constant from info.plist file once we get API's
var request : URLRequest?
var session : URLSession?
static func instance() -> HttpClientApi{
return HttpClientApi()
}
func makeAPICall(url: String,params: Dictionary<String, Any>?, method: HttpMethod, success:#escaping ( Data? ,HTTPURLResponse? , NSError? ) -> Void, failure: #escaping ( Data? ,HTTPURLResponse? , NSError? )-> Void) {
request = URLRequest(url: URL(string: url)!)
logging.print("URL = \(url)")
if let params = params {
let jsonData = try? JSONSerialization.data(withJSONObject: params, options: .prettyPrinted)
request?.setValue("application/json", forHTTPHeaderField: "Content-Type")
request?.httpBody = jsonData//?.base64EncodedData()
//paramString.data(using: String.Encoding.utf8)
}
request?.httpMethod = method.rawValue
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 30
configuration.timeoutIntervalForResource = 30
session = URLSession(configuration: configuration)
//session?.configuration.timeoutIntervalForResource = 5
//session?.configuration.timeoutIntervalForRequest = 5
session?.dataTask(with: request! as URLRequest) { (data, response, error) -> Void in
if let data = data {
if let response = response as? HTTPURLResponse, 200...299 ~= response.statusCode {
success(data , response , error as? NSError)
} else {
failure(data , response as? HTTPURLResponse, error as? NSError)
}
}else {
failure(data , response as? HTTPURLResponse, error as? NSError)
}
}.resume()
}
}
Now you can refer below code to get how to make an API call.
var paramsDictionary = [String:Any]()
paramsDictionary["username"] = "BBB"
paramsDictionary["password"] = "refef"
HttpClientApi.instance().makeAPICall(url: "Your URL", params:paramsDictionary, method: .POST, success: { (data, response, error) in
// API call is Successfull
}, failure: { (data, response, error) in
// API call Failure
})
I think you should pass your request instead of the url to session.dataTask
here is how my code looks like:
private let url = URL(string: "http://example.com/")!
func httpPost(jsonData: Data) {
if !jsonData.isEmpty {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = jsonData
URLSession.shared.getAllTasks { (openTasks: [URLSessionTask]) in
NSLog("open tasks: \(openTasks)")
}
let task = URLSession.shared.dataTask(with: request, completionHandler: { (responseData: Data?, response: URLResponse?, error: Error?) in
NSLog("\(response)")
})
task.resume()
}
}
Here is the Example of POST API for calling Login API with parameters "emailaddress" and "password" with userEmailID and Userpassword as two strings holding values for email and password respectively.
You can call this POST API anywhere in your view controller, as given below:
self.postLoginCall(url: "Your post method url") example: self.postLoginCall(url: "http://1.0.0.1/api/login.php")
func postLoginCall(url : String){
let request = NSMutableURLRequest(url: NSURL(string: url)! as URL)
request.httpMethod = "POST"
let postString = "emailaddress=\(userEmailID!)&password=\(Userpassword!)"
print(postString)
request.setValue("application/x-www-form-urlencoded; charset=utf-8", forHTTPHeaderField: "Content-Type")
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
guard error == nil && data != nil else { // check for fundamental networking error
print("error=\(error)")
return
}
do {
if let responseJSON = try JSONSerialization.jsonObject(with: data!) as? [String:AnyObject]{
print(responseJSON)
print(responseJSON["status"]!)
self.response1 = responseJSON["status"]! as! Int
print(self.response1)
//Check response from the sever
if self.response1 == 200
{
OperationQueue.main.addOperation {
//API call Successful and can perform other operatios
print("Login Successful")
}
}
else
{
OperationQueue.main.addOperation {
//API call failed and perform other operations
print("Login Failed")
}
}
}
}
catch {
print("Error -> \(error)")
}
}
task.resume()
}
Hello everyone I share below an example of a function to make a request in POST with SWIFT 5+.
This function allows you to send a POST request with an API entry point and parameters in the form of [[String: String]] and an Int to determine the output action.
For the output actions we call a function with Switch Case.
The operation is extremely simple. You have to put the two functions in one of your classes.
func MGSetRequestApi(endpoint: String, parameters: [[String: String]], MGSetAction: Int) -> String {
var setReturn: String!
let semaphore = DispatchSemaphore (value: 0)
var MGGetParam: String! = ""
for gate in parameters {
for (key, value) in gate {
let myParam = key + "=" + value + "&"
MGGetParam.append(contentsOf: myParam)
}
}
let postData = MGGetParam.data(using: .utf8)
var request = URLRequest(url: URL(string: endpoint)!,timeoutInterval: 10000)
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
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))
semaphore.signal()
return
}
print(String(data: data, encoding: .utf8)!)
setReturn = String(data: data, encoding: .utf8)!
DispatchQueue.main.async {
self.MGRequestAction(MGGetIdRq: MGSetAction, MGGetData: setReturn)
}
semaphore.signal()
}
task.resume()
semaphore.wait()
return setReturn
}
Then implement this function to manage the outputs
func MGRequestAction(MGGetIdRq: Int, MGGetData: String) {
switch MGGetIdRq {
case 1:
// Do something here
case 2:
// Do something else here
case 3:
// Do something else here again
default:
print("Set default action");
}
}
How to use this, you have two possibilities, the first one is to process what the function
MGSetRequestApi(endpoint: String, parameters: [[String: String]], MGSetAction: Int) -> String
returns (String) or to pass by the function
MGRequestAction(MGGetIdRq: Int, MGGetData: String)
which will call your Json parse function.
The MGRequestAction() function takes for parameter an Int for the choice of the action and the String of the return of the request
Now to use it do like this:
_ = MGSetRequestApi(endpoint: MY_END_POINT_API,
parameters: [["KEY_1": "VALUE 1"],
["KEY_2": "VALUE 2"],
["KEY_3": "VALUE 3"],
["KEY_4": "VALUE 4"]],
MGSetAction: 3)