How to parse nested JSON using custom completion block with JSONDecoder? - ios

i have nested json object, and need to parse and take fill out the collectionViewCell with imageUrls in the json
i need to iterate overe list.recommendBannerImages and save it in external var so that i can use it into my collectionViewCells

Firstly you write a function for this and lets assume that this function name fetchData and your data model should be conform Codable protocol.
func fetchData(url: String, completion: #escaping(Result<[DataModel], Error>)->()){
guard let url = URL(string: url) else { return }
URLSession.shared.dataTask(with: url){(data, response, error) in
if error != nil{
completion(.failure(error!))
print(error!)
} else{
guard let _ = response as? HTTPURLResponse, let jsonData = data else { return }
let yourDataModel = try? JSONDecoder().decode([DataModel].self, from: jsonData)
guard let dataModels = mainPageContentData else { return } //dataModels is array of DataModel
completion(.success(dataModels)) // And we send this array to completion blocks
}
}.resume()
}
And you should use this function like this
fetchData { (result) in //Trailing closure syntax
switch result {
case .failure(let error):
print(error)
case .success(let datas):
for data in datas{
print(data)
}
}
}

Related

Swift - How do I show Data in Swift View from Data.swift file?

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.

How can i make my Networking class generic in Swift?

Here i am extracting data as DataModel. But i want to make this class generic and pass the model myself so that i can use it to parse data from multiple API's. Can Anyone Help?
import Foundation
struct NetworkManager {
func fetchData(url : String, completion : #escaping (DataModel?) -> ()) {
print("Neeraj here")
let sessionURL = URL(string: url)
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: sessionURL!) { (data, response, error) in
if error == nil {
if let safeData = data {
if let parsedData = self.parseData(data : safeData) {
print("got data")
completion(parsedData)
}
else {
debugPrint("failed to fetch data")
completion(nil)
}
}
}
else {
print("error in data task is \(String(describing: error))")
completion(nil)
}
}
dataTask.resume()
}
func parseData(data : Data) -> DataModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(DataModel.self, from: data)
return decodedData
} catch {
print("error while parsing data \(error)")
return nil
}
}
}
With the convenient Result type you can write a quite tiny generic method, it returns the decoded type on success and any error on failure
func fetchData<T: Decodable>(urlString: String, completion: #escaping (Result<T,Error>) -> Void) {
guard let url = URL(string: urlString) else { return } // or throw an error
URLSession.shared.dataTask(with: url) { (data, _, error) in
if let error = error { completion(.failure(error)); return }
completion( Result{ try JSONDecoder().decode(T.self, from: data!) })
}.resume()
}
Note: Force unwrapping data! is 100% safe if no error occurs
Be aware that you have to specify the concrete type when you are going to call the method
fetchData(urlString: "https://example.com/api") { (result : Result<MyModel,Error>) in
switch result {
case .success(let model): print(model)
case .failure(let error): print(error)
}
}
You can add a generic type constraint (called Model) which conforms Decodable like below:
struct NetworkManager {
func fetchData<Model: Decodable>(url : String, completion : #escaping (Model?) -> ()) {
let sessionURL = URL(string: url)
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: sessionURL!) { (data, response, error) in
if error == nil {
if let safeData = data {
do {
let decodedData = try JSONDecoder().decode(Model.self, from: safeData)
completion(decodedData)
} catch {
print("error while parsing data \(error)")
}
} else {
debugPrint("failed to fetch data")
completion(nil)
}
}
else {
print("error in data task is \(String(describing: error))")
completion(nil)
}
}
dataTask.resume()
}
}
Usage
struct SampleModel: Decodable {
let name: String
}
NetworkManager().fetchData(url: "") { (data: SampleModel?) in
print(data)
}
You can write a generic function to fetch data like this one :
func fetchGenericData<T: Decodable>(urlString: String, completion: #escaping (T) -> ()) {
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!) { (data, resp, err) in
if let err = err {
print("Failed to fetch data:", err)
return
}
guard let data = data else { return }
do {
let obj = try JSONDecoder().decode(T.self, from: data)
completion(obj)
} catch let jsonErr {
print("Failed to decode json:", jsonErr)
}
}.resume()
}
}
I suppose that you have a data model, if you have not, you should create for your every object. Also by using a dummy URL i will make a request and fetch the JSON includes some users name and ids with JSON format.
Let`s define a data model for this:
struct StackUser: Decodable {
let id: Int
let name: String
}
fetchGenericData(urlString: "https://api.stackoverexample.com/stackusers") { (stackUsers: [StackUser]) in
stackUsers.forEach({print($0.name)})
}
Finally you will be parse the data and prints like this:
Rob
Matt
Vadian

How to extract data from networking class into variable in same class so that it can be accessed from other class in ios

How to extract data from networking class into variable in same class so that it can be accessed from other class in ios
Code for Networking Class
import Foundation
struct NetworkManager {
func fetchData(url : String) {
print("Neeraj here")
let sessionURL = URL(string: url)
let session = URLSession(configuration: .default)
let dataTask = session.dataTask(with: sessionURL!) { (data, response, error) in
if error == nil {
if let safeData = data {
if let parsedData = self.parseData(data : safeData) {
print("got data")
}
}
}
else {
print("error in data task is \(String(describing: error))")
}
}
dataTask.resume()
}
func parseData(data : Data) -> DataModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(DataModel.self, from: data)
return decodedData
} catch {
print("error while parsing data \(error)")
return nil
}
}
}
Here where i am getting data, i want that data to be stored somewhere or in same class so that i can access it from class i am calling this method fetchData
You can use the closure to return the value out of the function. This practice is functional programming, almost using for async function.
func fetchData(url: String, completion: (DataModel?) -> ()) {
...
if let parsedData = self.parseData(data : safeData) {
print("got data")
completion(parsedData)
} else {
completion(nil)
}
}
And then, to use it:
NetworkManager().fetchData(url: "https://google.com", completion: { data in
// handle the “data”
})

Avoid Swift Calling methods in viewDidLoad at the same time and getting "Index out of range"

could you give me a hand here? I would like to know how to call a swift function once another one is over, I think it is calling both functions at the same time. This is my viewDidLoad:
override func viewDidLoad() {
super.viewDidLoad()
downloadJsonEscuelas(id_responsable: "5")
downloadJsonAnuncios(id_escuela:("\arrayEscuelas[0].id_escuela!)"))
}
The first function "downloadJsonEscuelas" fills the array "arrayEscuelas" with data, and the second function receives the data as a parameter:
downloadJsonEscuelas:
func downloadJsonEscuelas(id_responsable: String) {
guard let downloadURL = URL(string: Constantes.URLbase+"json/getescuelas.php?id="+id_responsable) else { return }
URLSession.shared.dataTask(with: downloadURL) { data, urlResponse,
error in
guard let data = data, error == nil, urlResponse != nil else {
print("Something went wrong")
return
}
print("Escuelas downloaded")
do
{
let downloadedEscuelas = try JSONDecoder().decode([Escuelas].self, from: data)
self.arrayEscuelas = downloadedEscuelas
DispatchQueue.main.async {
print("id escuela actual:"+self.idEscuelaActual!+":)")
self.escuelaTextBox.text = self.arrayEscuelas[0].escuela!
self.idEscuelaActual = "\(self.arrayEscuelas[0].id_escuela!)" as String
}
} catch let jsonErr {
print("Error: ", jsonErr)
}
}.resume()
}
downloadJsonAnuncios:
func downloadJsonAnuncios(id_escuela: String) {
guard let downloadURL = URL(string: Constantes.URLbase+"json/getanuncios.php?id="+id_escuela) else { return }
URLSession.shared.dataTask(with: downloadURL) { data, urlResponse,
error in
guard let data = data, error == nil, urlResponse != nil else {
print("Something went wrong")
return
}
print("downloaded")
do
{
let downloadedAnuncios = try JSONDecoder().decode([Anuncios].self, from: data)
self.arrayAnuncios = downloadedAnuncios
print(self.arrayAnuncios[0].titulo!)
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let jsonErr {
print("Error: ", jsonErr)
}
}.resume()
}
I think it calls both functions at the same time, so it's not filling the array in the first function. When I pass the parameter as a plain number everything goes well, something like:
downloadJsonAnuncios(id_escuela: "2")
I hope you could help me here, thank you so much.
Insert second call ( this downloadJsonAnuncios(id_escuela:("\arrayEscuelas[0].id_escuela!)")) ) inside the first call ( downloadJsonEscuelas(id_responsable: "5")) 's completion
func downloadJsonEscuelas(id_responsable: String) {
guard let downloadURL = URL(string: Constantes.URLbase+"json/getescuelas.php?id="+id_responsable) else { return }
URLSession.shared.dataTask(with: downloadURL) { data, urlResponse,
error in
guard let data = data, error == nil, urlResponse != nil else {
print("Something went wrong")
return
}
print("Escuelas downloaded")
do
{
let downloadedEscuelas = try JSONDecoder().decode([Escuelas].self, from: data)
self.arrayEscuelas = downloadedEscuelas
DispatchQueue.main.async {
print("id escuela actual:"+self.idEscuelaActual!+":)")
self.escuelaTextBox.text = self.arrayEscuelas[0].escuela!
self.idEscuelaActual = "\(self.arrayEscuelas[0].id_escuela!)" as String
}
// here
downloadJsonAnuncios(id_escuela:("\arrayEscuelas[0].id_escuela!)"))
} catch let jsonErr {
print("Error: ", jsonErr)
}
}.resume()
}
Why not call the second one in the closure of the first one
do {
let downloadedEscuelas = try JSONDecoder().decode([Escuelas].self, from: data)
self.arrayEscuelas = downloadedEscuelas
DispatchQueue.main.async {
print("id escuela actual:"+self.idEscuelaActual!+":)")
self.escuelaTextBox.text = self.arrayEscuelas[0].escuela!
self.idEscuelaActual = "\(self.arrayEscuelas[0].id_escuela!)" as String
}
downloadJsonAnuncios(id_escuela:("\arrayEscuelas[0].id_escuela!)"))
} catch let jsonErr {
print("Error: ", jsonErr)
}
}.resume()
By default the code in viewDidLoad runs synchronously on the UIThread.
Your API call is running on a separate thread and is allowing your other method to be executed before it's completed.
You should probably be using a completion handler in your method so your second method only gets called after your api call completes.
See this answer for implementing CompletionHandler

Missing return in a function expected to return 'NSURLSessionDataTask'

I am following a tutorial on accessing an api and parsing the result. I am following the tutorial word for word but I cannot run the program because of 'Missing return in a function expected to return 'NSURLSessionDataTask'
so I changed the return statement to "return NSURLSessionDataTask" but then got an error saying "Cannot convert return expression of type 'NSURLSessionDataTask.Type" to return type 'NSURLSessionDataTask'
How do i figure out the return type? do I even need a return? because in the tutorial there is not return statement (i tried without return as well).
func dataTaskWithRequest(request: NSURLRequest, completionHandler: (NSData?, NSURLResponse?, NSError?) -> Void)
-> NSURLSessionDataTask {
let postEndpoint: String = "http://jsonplaceholder.typicode.com/posts/1"
let urlRequest = NSURLRequest(URL: NSURL(string: postEndpoint)!)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: {
(data, response, error) in
guard let responseData = data else {
print("Error: did not recieve data")
return
}
guard error == nil else {
print("error calling GET on /posts/1")
print(error)
return
}
// parse the resutl as JSON, since that's what the API provieds
let post: NSDictionary
do {
post = try NSJSONSerialization.JSONObjectWithData(responseData, options: []) as! NSDictionary
} catch {
print("error trying to convert data to JSON")
return
}
// now we have the post, let's just print it to prove we can access it
print("The post is: " + post.description)
if let postTitle = post["title"] as? String {
print("The title is: " + postTitle)
}
})
// and send it
task.resume()
}
Did you really mean to write your own method called dataTaskWithRequest which looks just like the NSURLSession method of the same name? The problem is that you said you're writing a method that returns a NSURLSessionTask object, but you don't return anything.
I'd think you meant something like the following, renaming your own method to something else, and specifying that it's not returning anything itself, because it's not:
func performRequest() {
let postEndpoint: String = "http://jsonplaceholder.typicode.com/posts/1"
let urlRequest = NSURLRequest(URL: NSURL(string: postEndpoint)!)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: {
(data, response, error) in
guard let responseData = data else {
print("Error: did not recieve data")
return
}
guard error == nil else {
print("error calling GET on /posts/1")
print(error)
return
}
// parse the resutl as JSON, since that's what the API provieds
let post: NSDictionary
do {
post = try NSJSONSerialization.JSONObjectWithData(responseData, options: []) as! NSDictionary
} catch {
print("error trying to convert data to JSON")
return
}
// now we have the post, let's just print it to prove we can access it
print("The post is: " + post.description)
if let postTitle = post["title"] as? String {
print("The title is: " + postTitle)
}
})
// and send it
task.resume()
}

Resources