I successfully get data from RSS feeds all the time but I am having a problem with this one and for the life of me I can't tell what the issue is.
Below code executes, returns 200 status code and no content
let urlTMC = URL(string: "https://teslamotorsclub.com/tmc/forums/model-s.73/index.rss")!
let urlRequest = NSMutableURLRequest(url: urlTMC as URL)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest as URLRequest, completionHandler: { data, response, error in
print(data)
print(response)
print(error)
if let response = response, let data = data {
do {
let jsonData = JSON(data: data)
print(jsonData)
} catch {
print("JSONSerial error")
}
}
else {
print(error!)
}
DispatchQueue.main.async(execute: {
print("DISPATCH")
})
})
task.resume()
Related
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()
}
In my application I am working on Alamofire to fetch the data . I think it is taking little bit more time to fetch json data. I am adding my code here. Can anyone suggest me how to reduce the fetching time?
func getUserData(completion: #escaping LcodeResponseCompletion) {
guard let url = URL(string: "\(LCODE_URL)") else{return}
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = HTTPMethod.get.rawValue
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)}
let jsonDecoder = JSONDecoder()
do {
let lcode = try jsonDecoder.decode(Empty.self, from: data)
completion(lcode)
} catch {
debugPrint(error.localizedDescription)
completion(nil)
}
}
}
I'm trying to make an async API get request to openweathermap.org 's API. The result should be this JSON structure. I'm particularly trying to get the temperature. I was taught to work with it by wrapping the JSON to a dictionary. Thing is I don't know what I can use to specify the object "main" (in the JSON) and get the temperature. Do I have to iterate object by object? This is my code so far (side note: is it worrying that my app uses 50 mb of RAM?)
let url = URL(string: stringURL)
let myQ = DispatchQueue.init(label: "getCityDetails")
myQ.async {
let session = URLSession.shared
let m = session.dataTask(with: url!, completionHandler: {(data, response, error) in
if let error = error {
print(error.localizedDescription)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Error with the response, unexpected status code: \(String(describing: response))")
return
}
do {
if let d = data{
let dictionaryObj = try JSONSerialization.jsonObject(with: d, options: []) as! NSDictionary
print(dictionaryObj)
}
}catch{
print(error.localizedDescription)
}
})
m.resume()
The first point is that the default URLSession works in a background thread so you dont need to create a dispatch queue (alos you are not using it correctly). The second point tries to use optional data not to use try/catch. Finally you could try to use Swift 5 together to the protocol Codable to have better code, simple and secure.
let url = URL(string: "https://samples.openweathermap.org/data/2.5/weather?q=London,uk&appid=439d4b804bc8187953eb36d2a8c26a02")!
URLSession.shared.dataTask(with: url, completionHandler: {(data, response, error) in
if let error = error {
print(error.localizedDescription)
return
}
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
print("Error with the response, unexpected status code: \(String(describing: response))")
return
}
guard let data = data else {
return
}
guard let dictionaryObj = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] else {
return
}
if let main = dictionaryObj["main"] as? [String: Any], let temperature = main["temp"] {
DispatchQueue.main.async {
print("Temperature: \(temperature)")
}
}
}).resume()
I'm trying to make this method accessible throughout the app because there are many view controllers need JSON response depending on the path and the language parameters, but I'm not sure what pattern to use or how to structure the app.
func fetchJsonFor(path: String, langugae: String) -> AnyObject{
var components = URLComponents()
components.scheme = Constants.APIScheme
components.host = Constants.APIHost
components.path = Constants.APIPath
components.path.append(path)
components.path.append(langugae)
let request = URLRequest(url: components.url!)
var parsedJSON: AnyObject!
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if error != nil{
print(error?.localizedDescription ?? "Error")
return
}
guard let data = data else{
return
}
do{
parsedJSON = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as AnyObject
} catch{
print("Can't parse JSON: \(data)")
return
}
}
task.resume()
return parsedJSON
}
You can go for Single Tone Design pattern.
Also remember you can't return the URLRequest response as functions return. It is a asynchronous task which not works in main thread. So return will not work.
You need to make use of closure ----> a completion block will more suitable.
class WebService {
static let shared = WebService()
func fetchJsonFor(path: String, langugae: String,completion:((Any?) -> Void)){
var components = URLComponents()
components.scheme = Constants.APIScheme
components.host = Constants.APIHost
components.path = Constants.APIPath
components.path.append(path)
components.path.append(langugae)
let request = URLRequest(url: components.url!)
var parsedJSON: AnyObject!
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if error != nil{
print(error?.localizedDescription ?? "Error")
completion(nil)
}
guard let data = data else{
completion(nil)
}
do{
parsedJSON = try JSONSerialization.jsonObject(with: data, options: .allowFragments)
completion(parsedJSON)
} catch{
print("Can't parse JSON: \(data)")
completion(nil)
}
}
task.resume()
}
}
How to use..
From your ViewController Class you can call web service like
WebService.shared.fetchJsonFor(path: "YOUR_PATH", langugae: "YOUR_LANGUAGE") { (response) in
if let response = response{
// Success response
}else{
//Failed response
}
}
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.