Create object using multiple independent API calls - json

I am working on a practice project with the CoinGecko API. I just figured out how to successfully fetch, parse, and display data from the API, but I have ran into an issue. Currently I am only fetching a single coin, using a single URL but I would like to be able to fetch multiple (3 in this case), all at once and display them. Not sure how to proceed, or if my explanation makes sense so I have attached my code below. Thanks in advance.
CoinListViewModel
import Foundation
class CoinListViewModel {
private(set) var coin: Coin
init(coin: Coin) {
self.coin = coin
}
func getCoins(url: URL) async {
do {
let coin = try await WebService().getCoins(url: url)
self.coin = coin
} catch {
print(error)
}
}
}
struct CoinViewModel {
private let coin: Coin
init(coin: Coin) {
self.coin = coin
}
var symbol: String {
coin.symbol
}
var name: String {
coin.name
}
var price: [String: Double] {
coin.marketData.currentPrice
}
}
WebService
import Foundation
enum CoinsError: Error {
case invalidServerResponse
}
class WebService {
func getCoins(url: URL) async throws -> Coin {
let (data, response) = try await URLSession.shared.data(from: url)
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
throw CoinsError.invalidServerResponse
}
return try JSONDecoder().decode(Coin.self, from: data)
}
}
Constants
import Foundation
struct Constants {
struct coinURLs {
static let allCoinURLs = [
URL(string: "https://api.coingecko.com/api/v3/coins/bitcoin")!,
URL(string: "https://api.coingecko.com/api/v3/coins/ethereum")!,
URL(string: "https://api.coingecko.com/api/v3/coins/litecoin")!
]
}
}
Coin
import Foundation
// MARK: - WelcomeElement
struct Coin: Codable {
let symbol, name: String
let marketData: MarketData
enum CodingKeys: String, CodingKey {
case symbol, name
case marketData = "market_data"
}
static var DefaultCoin = Coin(symbol: " ", name: " ", marketData: MarketData(currentPrice: ["usd": 0.0]))
}
struct Tion: Codable {
let en: String
enum CodingKeys: String, CodingKey {
case en
}
}
struct MarketData: Codable {
let currentPrice: [String: Double]
enum CodingKeys: String, CodingKey {
case currentPrice = "current_price"
}
}
CoinListViewController
import Foundation
import UIKit
class CoinListViewController: UITableViewController {
private let vm = CoinListViewModel(coin: Coin.DefaultCoin)
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
Task {
await getCoins()
}
}
private func configureUI() {
self.navigationController?.navigationBar.prefersLargeTitles = true
self.title = "CoinFlip"
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "StockCell")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "StockCell", for: indexPath)
let coin = vm.coin
var content = cell.defaultContentConfiguration()
content.text = coin.name
content.secondaryText = "$\(Int(coin.marketData.currentPrice["usd"] ?? 0))"
cell.contentConfiguration = content
return cell
}
private func getCoins() async {
await vm.getCoins(url: Constants.coinURLs.allCoinURLs[0])
print(vm.coin)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}

You can spawn multiple network calls if you make a few changes to your WebService. This will also return a partial result if fetching a coin fails.
func getCoins(urls: [URL]) async throws -> [Coin] {
return try await withThrowingTaskGroup(of: Coin.self, body: { taskGroup in
var coins = [Coin]()
urls.forEach { url in
taskGroup.addTask {
let (data, response) = try await URLSession.shared.data(from: url)
guard
let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200
else { return Coin() }
return try JSONDecoder().decode(Coin.self, from: data)
}
}
for try await coin in taskGroup {
coins.append(coin)
}
return coins
})
}

Related

The data couldn’t be read in Swift

I am new to swift . I am trying to retrieve the data form api. The data in json format. Here is the data in json format.
{
"photos": [
{
"id": 424926,
"sol": 1000,
"camera": {
"id": 22,
"name": "MAST",
"rover_id": 5,
"full_name": "Mast Camera"
},
"img_src": "http://mars.jpl.nasa.gov/msl-raw-images/msss/01000/mcam/1000ML0044631200305217E01_DXXX.jpg",
"earth_date": "2015-05-30",
"rover": {
"id": 5,
"name": "Curiosity",
"landing_date": "2012-08-06",
"launch_date": "2011-11-26",
"status": "active"
}
}
]
}
Here is the online Json viewer .
NetworkMnager code .
class NetworkManager{
func fetchData(completion: #escaping ([Rover]) -> Void) {
if let url = URL(string: "https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&api_key=Mykey") {
URLSession.shared.dataTask(with: url) { data, urlResponse, error in
if let data = data {
do {
let result = try JSONDecoder().decode([Rover].self, from: data)
completion(result)
} catch let error {
print(error.localizedDescription)
}
}
}
.resume()
}
}
}
I am trying to display the data id and status from rover (struct ) into simulator when i try to run the data , I am getting following error into console window of the xcode.The data couldn’t be read because it isn’t in the correct format. I am sure there is somethings wrong with the struct. Here is the struct code .
import Foundation
// MARK: - Welcome
struct Welcome: Codable {
let photos: [Photo]
}
// MARK: - Photo
struct Photo: Codable {
let id, sol: Int
let camera: Camera
let imgSrc: String
let earthDate: String
let rover: Rover
enum CodingKeys: String, CodingKey {
case id, sol, camera
case imgSrc = "img_src"
case earthDate = "earth_date"
case rover
}
}
// MARK: - Camera
struct Camera: Codable {
let id: Int
let name: String
let roverID: Int
let fullName: String
enum CodingKeys: String, CodingKey {
case id, name
case roverID = "rover_id"
case fullName = "full_name"
}
}
// MARK: - Rover
struct Rover: Codable {
let id: Int
let name, landingDate, launchDate, status: String
enum CodingKeys: String, CodingKey {
case id, name
case landingDate = "landing_date"
case launchDate = "launch_date"
case status
}
}
Table view controller code .
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
private var posts = [Rover]()
private let networkManager = NetworkManager()
private var rowSelected = 0
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
setUpUI ()
fetchData()
}
/* override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showDetail" {
let destination = segue.destination as? SecondViewController
destination?.posts = posts
destination?.rowSelected = rowSelected
}
}*/
private func setUpUI () {
tableView.dataSource = self
tableView.delegate = self
}
private func fetchData() {
networkManager.fetchData { [weak self] array in
self?.posts = array
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}
}
extension ViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
rowSelected = indexPath.row
performSegue(withIdentifier: "cell", sender: nil)
}
}
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let row = indexPath.row
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let post = posts[row]
cell.textLabel?.text = String(post.id)
cell.detailTextLabel?.text = post.status
return cell
}
}
try using this example code to decode your json data:
(note I do not have a api_key, so I cannot test this).
EDIT-1:
here is the code I used for testing (sorry it is using SwiftUI), from this you should be
able to get your data decoded. Works for me.
class NetworkManager {
func fetchData(completion: #escaping ([Photo]) -> Void) { // <-- here
if let url = URL(string: "https://api.nasa.gov/mars-photos/api/v1/rovers/curiosity/photos?sol=1000&api_key=DEMO_KEY") {
URLSession.shared.dataTask(with: url) { data, urlResponse, error in
if let data = data {
do {
let result = try JSONDecoder().decode(NASAResponse.self, from: data) // <-- here
completion(result.photos)
} catch let error {
print(error.localizedDescription)
}
}
}
.resume()
}
}
}
struct ContentView: View {
let networkManager = NetworkManager()
#State var photos: [Photo] = []
var body: some View {
List (photos) { photo in
HStack {
Text(String(photo.rover.id))
Text(photo.rover.status.rawValue)
}
}
.onAppear {
networkManager.fetchData { thePhotos in
photos = thePhotos
}
}
}
}
struct NASAResponse: Codable {
let photos: [Photo]
}
// MARK: - Photo
struct Photo: Identifiable, Codable {
let sol, id: Int
let earthDate: String
let camera: Camera
let imgSrc: String
let rover: Rover
enum CodingKeys: String, CodingKey {
case sol, id
case earthDate = "earth_date"
case camera
case imgSrc = "img_src"
case rover
}
}
// MARK: - Camera
struct Camera: Identifiable, Codable {
let id, roverID: Int
let fullName: FullName
let name: CameraName
enum CodingKeys: String, CodingKey {
case id
case roverID = "rover_id"
case fullName = "full_name"
case name
}
}
enum FullName: String, Codable {
case chemistryAndCameraComplex = "Chemistry and Camera Complex"
case frontHazardAvoidanceCamera = "Front Hazard Avoidance Camera"
case mastCamera = "Mast Camera"
case navigationCamera = "Navigation Camera"
case rearHazardAvoidanceCamera = "Rear Hazard Avoidance Camera"
}
enum CameraName: String, Codable {
case chemcam = "CHEMCAM"
case fhaz = "FHAZ"
case mast = "MAST"
case navcam = "NAVCAM"
case rhaz = "RHAZ"
}
// MARK: - Rover
struct Rover: Identifiable, Codable {
let status: Status
let id: Int
let landingDate, launchDate: String
let name: RoverName
enum CodingKeys: String, CodingKey {
case status, id
case landingDate = "landing_date"
case launchDate = "launch_date"
case name
}
}
enum RoverName: String, Codable {
case curiosity = "Curiosity"
}
enum Status: String, Codable {
case active = "active"
}
EDIT-2:
To get all the "rover" into the post var, in your class ViewController: UIViewController, do this
for your tableView :
private func fetchData() {
networkManager.fetchData { thePhotos in
self.posts = thePhotos.map { $0.rover }
DispatchQueue.main.async {
self?.tableView.reloadData()
}
}
}

swiftUI / Xcode 12 fetch data from server

I'm very new to swiftUI and have been working through the landscapes app tutorial.
I have been trying to switch the data source from a bundled JSON file to a remote JSON source but have so far been lost on how to integrate what I've learnt about the URLSession with the tutorials load code.
Apple's code:
final class ModelData: ObservableObject {
#Published var landmarks: [Landmark] = load("landmarkData.json")
// #Published var landmarks: [Landmark] = apiCall.getLocations(locations)
}
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
What I have to load from the remote source:
struct Location: Codable, Identifiable {
let id = UUID()
let country: String
let name: String
}
class apiCall {
func getLocations(completion:#escaping ([Location]) -> ()) {
guard let url = URL(string: "https://overseer.cyou/heritage/heritageData.json") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
let locations = try! JSONDecoder().decode([Location].self, from: data!)
print(locations)
DispatchQueue.main.async {
completion(locations)
}
}
.resume()
}
}
Can anyone show me how I go about doing this, ideally from a complete beginners point of view?
// framework support
import SwiftUI
import Combine
// List view setup
struct LocationsView: View {
#ObservedObject var viewModel = LocationModel()
var body: some View {
List(viewModel.locations) { location in
HStack {
VStack(alignment: .leading) {
Text(location.name)
.font(.headline)
Text(location.country)
.font(.subheadline)
}
}
}
}
}
// Location model
struct Location: Codable, Identifiable {
var id = UUID()
let country: String
let name: String
let locationId: Int = 0
enum CodingKeys: String, CodingKey {
case locationId = "id"
case country
case name
}
}
// Location view model class
class LocationModel: ObservableObject {
#Published var locations: [Location] = []
var cancellationToken: AnyCancellable?
init() {
getLocations()
}
}
extension LocationModel {
func getLocations() {
cancellationToken = self.request("https://overseer.cyou/heritage/heritageData.json")?
.mapError({ (error) -> Error in
print(error)
return error
})
.sink(receiveCompletion: { _ in },
receiveValue: {
self.locations = $0
})
}
// API request
private func request(_ path: String) -> AnyPublisher<[Location], Error>? {
guard let url = URL(string: path)
else { return nil }
let request = URLRequest(url: url)
return apiCall.run(request)
.map(\.value)
.eraseToAnyPublisher()
}
}
// API setup
struct apiCall {
struct Response<T> {
let value: T
let response: URLResponse
}
static func run<T: Decodable>(_ request: URLRequest) -> AnyPublisher<Response<T>, Error> {
return URLSession.shared
.dataTaskPublisher(for: request)
.tryMap { result -> Response<T> in
let value = try JSONDecoder().decode(T.self, from: result.data)
return Response(value: value, response: result.response)
}
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}

swift json help in displaying data on table view controller

// my url
// https://fetch-hiring.s3.amazonaws.com/hiring.json
/*
my json
[
{"id": 383, "listId": 4, "name": ""},
{"id": 472, "listId": 1, "name": ""},
{"id": 625, "listId": 2, "name": null}
]
*/
// my main vc class for table view controller
import UIKit
class HeadlLinesTableViewController: UITableViewController {
var parse = [HiringElement]()
override func viewDidLoad() {
// Do any additional setup after loading the view.
super.viewDidLoad()
// Do any additional setup after loading the view.
let urlString = "https://fetch-hiring.s3.amazonaws.com/hiring.json"
guard let url = URL(string: urlString) else { return }
// 2
URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let data = data else { return }
// 3
//Decode data
self.Elements = try? JSONDecoder().decode(HiringElement.self, from: data)
print(data)
// 4
//Get back to the main queue
DispatchQueue.main.async {
self.tableView.reloadData()
}
// 5
}.resume() // fires of request
}
My model struct for my api this is something I used from quickTypeIo api generator
struct HiringElement: Codable {
let id, listID: Int
let name: String?
enum CodingKeys: String, CodingKey {
case id
case listID
case name
}
} typealias Hiring = [HiringElement]
And my table view controller method here I can't display data on and some some errors. I am using tableview controller so doesn't need tableview delegate or datasource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
guard let articles = Elements else { return 0 }
return return parse.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
as? newsTableViewCell else {
fatalError(" cell not found ") }
// here I have errors thanks
cell.titleLabel.text = parse[indexPath.row].name
print(cell.titleLabel.text)
return cell
}
}
Here is my table view cell class
import UIKit
class newsTableViewCell: UITableViewCell {
//var article:Article!
#IBOutlet weak var avator: UIImageView!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var newsLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
I think you should work on the decoding part. Here is a solution:
struct HiringElement: Decodable {
let id, listId: Int
let name: String?
}
#propertyWrapper
struct IgnoreFailure<Value: Decodable>: Decodable {
var wrappedValue: [Value] = []
private struct _None: Decodable {}
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
while !container.isAtEnd {
if let decoded = try? container.decode(Value.self) {
wrappedValue.append(decoded)
}
else {
try? container.decode(_None.self)
}
}
}
}
Then write the following code in your HeadlLinesTableViewController.swift.
typealias ArrayIgnoringFailure<Value: Decodable> = IgnoreFailure<Value>
Then try decoding as:
guard let objects = try? JSONDecoder().decode(ArrayIgnoringFailure<HiringElement>.self, from: data) else { return }
self.elements = objects.wrappedValue
Hope it will solve your problems.

My object array is nil while my data are correct

I try to display my data in a tableView using no framework to parse my data, but when I add my data to my table and debug it, it is nil at the output while my data I retrieve are well parses, have I forgotten something to do?
I use a structure for my parameters as this :
enum Types {
case School
case Hospital
case Station_Essence
case Restaurant
}
struct Adresse {
public var title: String
public var details: String?
public var type: Types
public var coordinate: [String: Any]
}
and in my ViewController, i proced as this :
class ListMapViewController: UIViewController {
#IBOutlet var TitleTableView: UITableView!
#IBOutlet var MapView: MKMapView!
var adresse: [Adresse]?
override func viewDidLoad() {
super.viewDidLoad()
self.TitleTableView.register(UINib(nibName: "ListMapTableViewCell", bundle: nil), forCellReuseIdentifier: "Adresse")
self.TitleTableView.delegate = self
self.TitleTableView.dataSource = self
guard let POI = URL(string: "https://moc4a-poi.herokuapp.com/") else {
return
}
let task = URLSession.shared.dataTask(with: POI) { (data, response, error) in
guard let dataResponse = data else { return }
if let json = try! JSONSerialization.jsonObject(with: dataResponse, options:[]) as? [[String: Any]] {
for data in json {
let title = data["title"] as! String
let details = data["details"] as? String
guard let type = data["type"] as? Int else { return }
let valueType = self.valueType(dataType: type)
guard let coordinates = data["coordinates"] as? [String: Any] else { return }
self.adresse?.append(Adresse(title: title, details: details, type: valueType, coordinate: coordinates))
}
}
print(self.adresse)
}
self.TitleTableView.reloadData()
task.resume()
}
private func valueType(dataType: Int) -> Types {
if(dataType == 1) {
return Types.School
} else if (dataType == 2) {
return Types.Hospital
} else if (dataType == 3) {
return Types.Station_Essence
} else {
return Types.Restaurant
}
}
}
extension ListMapViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.adresse?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Adresse", for: indexPath) as! ListMapTableViewCell
if let adresse = self.adresse?[indexPath.row] {
cell.draw(adresse: adresse)
}
return cell
}
}
extension ListMapViewController: UITableViewDelegate {
}
You have two big problems.
self.adresse is nil. You never assign it a value. So all of the self.adresse?... do nothing.
You call reloadData too soon. It needs to be done inside the completion block, after you update the data. And it needs to be on the main queue.
To fix #1, change var adresse: [Adresse]? to var adresse = [Adresse](). Then you can get rid of all the ? after uses of adresse.
To fix #2, add:
DispatchQueue.main.async {
self.TitleTableView.reloadData()
}
just after the print at the end of the completion block. Don't forget to remove the current call to reloadData.

Swift 3 Parse JSON into UITableView using URLSession

I am trying to parse JSON using URLSession and without using Alamofire or anything else.
I just want to take the JSON and put it into a UITableView.
I am trying to piece together what I learned from learning how to Parse JSON using Alamofire with what I can find on google. Many of the answers on youtube or Stack etc use NS for everything..NSURL, NSDictionary, etc etc..Or are just typing code without explaining what/why.
I THINK I am almost there, but I need help understanding what I have left to do.
SO.
I Allowed arbitrary loads in the plst
In a Swift File I have the following
class Potter {
private var _title: String!
private var _author: String!
private var _imageURL: String!
let POTTER_URL = "http://de-coding-test.s3.amazonaws.com/books.json"
var title: String {
if _title == nil {
_title = ""
}
return _title
}
var author: String {
if _author == nil {
_author = ""
}
return _author
}
var imageURL: String {
if _imageURL == nil {
_imageURL = ""
}
return _imageURL
}
func downloadJSON() {
let url = URL(string: POTTER_URL)
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print("Error")
} else {
if let content = data {
do {
if let jDict = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as? Dictionary<String, AnyObject> {
if let title = jDict["title"] as? String {
self._title = title.capitalized
}
if let author = jDict["author"] as? String {
self._author = author.capitalized
}
if let imgURL = jDict["imageURL"] as? String {
self._imageURL = imgURL
}
}
}
catch {
}
}
}
}
task.resume()
}
}
In my Main.Storyboard I added the tableview and set up all the UI, and in my ViewController I have set up the tableview delegates.
I created a property of
var potters = [Potter]()
I am stuck now on how to I populate this array, and how do I set up the proper threading
First of all your model is insane pretty weird.
In Swift never use backed private variables to get read/only properties. And never declare properties as implicit unwrapped optional because you are too lazy to write an initializer.
The entire model can be reduced to
class Potter {
let title, author, imageURL: String
init(title: String, author: String, imageURL : String) {
self.title = title
self.author = author
self.imageURL = imageURL
}
}
If you would use a struct, it's even
struct Potter {
let title, author, imageURL: String
}
because you get the memberwise initializer for free.
Secondly, put the method downloadJSON() out of the model and put it in the controller and call it in viewDidLoad().
In the controller declare the download URL and the data source array
let POTTER_URL = "http://de-coding-test.s3.amazonaws.com/books.json"
var books = [Potter]()
Your method downloadJSON() cannot work because the JSON object is an array ([]), not a dictionary ({}). You need a loop to iterate thru the items, get the values, create a Potter item respectively and append it to the data source. If a value does not exist, an empty string is assigned. Finally reload the table view on the main thread.
func downloadJSON() {
let url = URL(string: POTTER_URL)
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print("DataTask error", error!)
} else {
do {
if let bookData = try JSONSerialization.jsonObject(with: data!) as? [[String:String]] {
books.removeAll() // clear data source array
for book in bookData {
let title = book["title"] ?? ""
let author = book["author"] ?? ""
let imgURL = book["imageURL"] ?? ""
books.append(Potter(title: title, author: author, imageURL: imgURL))
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
catch {
print("Serialization error", error)
}
}
}
task.resume()
}
Two notes:
The standard JSON dictionary in Swift 3 is [String:Any], in this particular case it's even [String:String].
.mutableContainers is useless if the containers are only read and useless in Swift anyway because the object cannot be casted to NSMutableArray / -Dictionary and you get mutability for free using a variable.
The web services returns an array of objects: [Dictionary<String, AnyObject>].
It will be easier if you create a init method with a dictionary as parameter.
The downloadJSON is an async task, using completionHandler is the best way. And if you want to place the downloadJSON in the Potter class, it should be a static function.
Final, you should handle the result like this:
Potter.downloadJSON { potters in
self.potters = potters
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
The final code:
class ViewController: UIViewController {
var potters = [Potter]()
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
Potter.downloadJSON { potters in
self.potters = potters
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return potters.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
let potter = potters[indexPath.row]
cell.textLabel?.text = potter.title
cell.detailTextLabel?.text = potter.author
return cell
}
}
class Potter {
private var _title: String!
private var _author: String!
private var _imageURL: String!
static let POTTER_URL = "http://de-coding-test.s3.amazonaws.com/books.json"
var title: String {
if _title == nil {
_title = ""
}
return _title
}
var author: String {
if _author == nil {
_author = ""
}
return _author
}
var imageURL: String {
if _imageURL == nil {
_imageURL = ""
}
return _imageURL
}
init(dict: Dictionary<String, AnyObject>) {
self._title = dict["title"] as? String
self._imageURL = dict["imageURL"] as? String
self._author = dict["author"] as? String
}
class func downloadJSON(completion: #escaping (_ potters: [Potter]) -> Void) {
let url = URL(string: POTTER_URL)
let task = URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error != nil {
print("Error")
} else {
if let content = data {
do {
if let jArray = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as? [Dictionary<String, AnyObject>] {
var potters = [Potter]()
for jDict in jArray {
let potter = Potter(dict: jDict)
potters.append(potter)
}
completion(potters)
}
}
catch {
}
}
}
}
task.resume()
}
}
The method downloadJSON() should be implemented in the ViewController since it is returning the array of Potter data. Then in the URLSession response you should create one array which will be act as the tableview datasource. (i.e self.arrTableData = try JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers) as? [[String : AnyObject]])
Then in for the tableView
func tableView(_ tableView: UITableView, numberOfRowsInSection sectionIndex: Int) -> Int {
return self.arrTableData.count
}
and in cell for row at index path
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//create `potters` object with the value and use it else you can direcly use the value of objects as below.
let dictPotters = self.arrTableData[indexPath.row]
let title = dictPotters["title"]
}
Thanks