fill in UITableViewCell with JSONdata - ios

I am trying to display some json data inside my tableView cell, and parsed the json data.
But
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.contents.count
}
returning 0
and as a result, I cannot display the data inside the UITableViewCell eventhough I have all my JSON data have been parsed and ready to be displayed.. .
how to fix it?
my response Model class and the rest of implementation are as follows:
Model classes for json response
// MARK: - TradingPairElement
class TradingPair: Entity {
var id: Int?
//var name: String?
var quoteAsset: QuoteAsset?
}
enum QuoteAsset: String, Codable {
case btc = "BTC"
case krw = "KRW"
}
// MARK: - TickerByPair
class TickerByPair: Entity {
var ask: Int?
//var price: Double?
//var volume: Double?
var askVolume: Double?
var bid: Int?
var bidVolume: Double?
var time: String?
}
And a wrapper class for the above two class 's contents:
class Entity: Codable {
var name: String?
var price: Double?
var volume: Double?
}
and here is how i am getting the data from the api and assigning to my self variables:
func APIcall() {
ServerCommunicator.getPairs().done{ response -> Void in
for keyPathParam in response {
self.keyPathParam = keyPathParam.name
}
ServerCommunicator.getPair(with: self.keyPathParam).done{ ticker -> Void in
for data in self.contents {
data.name = ticker.name
data.price = ticker.price
data.volume = ticker.volume
self.contents.append(data)
}
}.catch{(err) in
print(err)
}
}.catch{(error) in
print(error)
}
}

First of all if the API sends always all keys declare the properties non-optional and as constants and most likely you don't need a class and conformance to Encodable
struct Entity: Decodable {
let name: String
let price: Double
let volume: Double
}
After getting the data from the server you have to create new instances of Entity and assign them to the data source array. Further you need DispatchGroup to handle the loop and reload the table view after the last entity has been created.
If you want to overwrite self.contents with the received data uncomment the removeAll line
func APIcall() {
ServerCommunicator.getPairs().done{ response in
// self.contents.removeAll()
let group = DispatchGroup()
for keyPathParam in response {
group.enter()
ServerCommunicator.getPair(with: keyPathParam.name).done{ ticker in
let entity = Entity(name: ticker.name, price: ticker.price, volume: ticker.volume)
self.contents.append(entity)
group.leave()
}.catch{(err) in
print(err)
group.leave()
}
}
group.notify(queue: .main) {
self.tableView.reloadData()
}
}.catch{(error) in
print(error)
}
}

Related

Decoding JSON into model using MVVM

I am unable to decode my JSON data into my model. This is my first time attempting to use MVVM pattern and for some reason I cannot figure out why I cannot get a reference to my model variables such as title or id. Besides the error shown below I also get a message saying 'This property is defined on Movie and may not be available in this context'
class MovieViewModel {
let movie: APIResponse
init(movie: APIResponse) {
self.movie = movie
}
var title: String {
return movie.data.title // Value of type [Movie] has no member 'title'
}
}
struct APIResponse: Codable {
var data: [Movie]
}
struct Movie: Codable {
var id: String
var title: String
}
class MovieListViewModel {
private var movieViewModels = [MovieViewModel]()
func addMovieViewModels(_ vm: MovieViewModel) {
movieViewModels.append(vm)
}
func numberOfRows(_ section: Int) -> Int {
return movieViewModels.count
}
func modelAtIndex(_ Index: Int) -> GiphyViewModel {
return movieViewModels[Index]
}
}
I think you should seperate API calls from the view model.
You can take example on this repo to know how to proceed.

Swift Error The data can not be Read because it is missing

I am trying to display the data from API . Here is the API Link .https://coinmap.org/api/v1/venues/ . I want to display the properties of the Vanues Array fields into IOS app . I created model by using Quick type . I use the Map like self.vanues = respone.results.map{$0} but still same result Here is the model .
import Foundation
// MARK: - Welcome
struct Coin: Codable {
let venues: [Venue]
}
// MARK: - Venue
struct Venue: Codable {
let id: Int
let lat, lon: Double
let category, name: String
let createdOn: Int
let geolocationDegrees: String
enum CodingKeys: String, CodingKey {
case id, lat, lon, category, name
case createdOn = "created_on"
case geolocationDegrees = "geolocation_degrees"
}
}
I convert that to list by using another swift file . Here is the code .
import Foundation
struct VanueResponse: Decodable {
let results: [Venue]
}
Here is my Network Manager .
import Foundation
class NetworkManager {
func getCoins(from url: String, completion: #escaping (Result<VanueResponse, NetworkError>) -> Void ) {
guard let url = URL(string: url) else {
completion(.failure(.badURL))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(.other(error)))
return
}
if let data = data {
//decode
do {
let response = try JSONDecoder().decode(VanueResponse.self, from: data)
completion(.success(response))
} catch let error {
completion(.failure(.other(error)))
}
}
}
.resume()
}
}
Here is the presenter class.
import Foundation
class VenuePresenter : VanueProtocol{
// creating instance of the class
private let view : VanueViewProtocol
private let networkManager: NetworkManager
private var vanues = [Venue]()
var rows: Int{
return vanues.count
}
// initilanize the class
init(view:VanueViewProtocol , networkmanager:NetworkManager = NetworkManager()){
self.view = view
self.networkManager = networkmanager
}
func getVanue(){
let url = "https://coinmap.org/api/v1/venues/"
networkManager.getCoins(from: url) { result in
switch result {
case.success(let respone):
self.vanues = respone.results
DispatchQueue.main.async {
self.view.resfreshTableView()
}
case .failure(let error):
DispatchQueue.main.async {
self.view.displayError(error.localizedDescription)
print(Thread.callStackSymbols)
}
}
}
}
func getId(by row: Int) -> Int {
return vanues[row].id
}
func getLat(by row: Int) -> Double {
return vanues[row].lat
}
func getCreated(by row: Int) -> Int {
return vanues[row].createdOn
}
func getLon(by row: Int) -> Double? {
return vanues[row].lon
}
}
I put the break point and find this in console windows .
Here is the screenshot when I run the Applications .
The Decoding Error is clear:
The key in the root dictionary is venues (not results) so the proper struct is Coin.
In getCoins replace both occurrences of VanueResponse with Coin

Сannot convert value of type "Obj" to expected argument type 'Obj'

I have an Array that comes to me from the server.
My problem is an error that appears when I want to add objects to an
array. How to solve this problem and work with an array of objects
that comes from the server ??
EXAMPLE OF RETURNED DATA
[
{
"empId": 1970083,
"empCode": "2007",
"empName": "Emp Test",
"monthClosed": 0,
"monthApproved": 0,
"approvedDate": 0,
"employerName": "Name",
"employerApproval": 1,
"employerApprovalDate": "2020-09-02 17:22:51.843"
},
]
This structure to receive this data
struct GetMonthSummaryObj: Codable {
var empId: Int?
var empCode: String?
var empName: String?
var monthClosed: Int?
var monthApproved: Int?
var approvedDate: Int?
var employerName: String?
var employerApproval: Int?
var employerApprovalDate: String?
}
This is a method in ViewModel to add data that came to an array and use it for a table. For example, to display or the number of cells in a table
func setEmployees(employees: [GetMonthSummaryObj?]) {
employeesList = []
employees.forEach {_ in
employeesList.append(CloseMonthEmpListItem(employee: employees))
//Error - Cannot convert value of type '[GetMonthSummaryObj?]' to expected argument type 'GetMonthSummaryObj'
}
}
Here I create an array object to work with it
class CloseMonthEmpListItem: Equatable, NSCopying {
var employee: GetMonthSummaryObj
init(employee: GetMonthSummaryObj) {
self.employee = employee
}
static func == (lhs: CloseMonthEmpListItem, rhs: CloseMonthEmpListItem) -> Bool {
return lhs.employee.empId == rhs.employee.empId
}
func copy(with zone: NSZone? = nil) -> Any {
let copy = CloseMonthEmpListItem(employee: employee)
return copy
}
}
This init(employee: GetMonthSummaryObj) accepts GetMonthSummaryObj type while you pass [GetMonthSummaryObj?] which won't work
You need to replace
employees.forEach {_ in
employeesList.append(CloseMonthEmpListItem(employee: employees))
}
With
let res = employees.compactMap{ $0 }
res.forEach { item in
employeesList.append(CloseMonthEmpListItem(employee:item))
}
or
let res = employees.compactMap{ $0 }
employeesList = res.map { CloseMonthEmpListItem(employee:$0) }

How to create a model class and access the values from JSON response using Codable in ios swift?

I'm learning how to create a model class and using Codable protocol how to access the values for JSON response. Using Alamofire get a request for API call. Here my code
my model class
class UserDic:Codable{
var batters:Batter?
var id:Int?
var name:String?
var topping:[Topping]?
var ppu:Int?
var type:String?
}
class Topping:Codable{
var type:String?
var id:Int?
}
class Batter:Codable{
var id:Int?
var type:String?
}
class ViewController: UIViewController {
#IBOutlet weak var userTbl: UITableView!
var url:String!
var toppingVal:[Topping] = []
override func viewDidLoad() {
super.viewDidLoad()
url = "http://www.json-generator.com/api/json/get/bUNhsLXzVK?indent=2"
Alamofire.request(url, method: .get, encoding: JSONEncoding.default)
.responseJSON { response in
print("response--->",response)
guard response.data != nil else { return }
do{
let jsonResponse = try JSONDecoder().decode(UserDic.self, from: Data(response.data!))
self.toppingVal = jsonResponse.topping!
self.userTbl.reloadData()
}
print("reslut pass the :\(String(describing: jsonResponse.type))")
}catch{
print("Failed pass the :\(error)")
}
}
}
extension ViewController: UITableViewDelegate,UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return toppingVal.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = userTbl.dequeueReusableCell(withIdentifier: "UserCell")
let aUser = toppingVal[indexPath.row]
cell?.textLabel?.text = String(aUser.id!)
return cell ?? UITableViewCell()
}
}
my question: Kindly view my json response and check my model class.How can i access the batters values and list in the UUtableview. Thanks advance.
You need
// MARK: - Empty
struct UserDic: Codable {
let topping: [Topping]
let name: String
let batters: Batters
let ppu: Double
let type: String
let id: Int
}
// MARK: - Batters
struct Batters: Codable {
let batter: [Topping]
}
// MARK: - Topping
struct Topping: Codable {
let type: String
let id: Int
}
let jsonResponse = try JSONDecoder().decode(UserDic.self, from: Data(response.data!))
self.toppingVal = jsonResponse.batters.batter
self.userTbl.reloadData()

TableView loaded before fetching data Firebase / Swift

i have a FireBase database, inside i have a table of products and another table of orders with ids of these products, what i am trying to do is to get products from table of products based on ids inside table of orders, since FireBase will only allow me to get the products one by one , my tableview is loaded before i get all products that are referenced inside the orders table.
heres how i did that :
struct Product: Decodable, Encodable{
var id: String?
var ref: String
var description: String
var type: String
var price: String
var qtyOrdred:Int?
}
struct Order:Decodable, Encodable {
var id: String?
var isValide: Bool
var madeBy: String
var info: String?
var ordredProd: [OrderedProduct]
}
struct OrderedProduct:Decodable, Encodable {
var id: String
var qty: Int
}
func getData(completion: #escaping ([Product])->Void){
var allProduct = [Product]()
for product in orderedProduct {
getProductWithKey(qty: product.qty, key: product.id) { (p) in
print(p.ref)
allProduct.append(p)
}
}
}
func getProductWithKey(qty: Int,key: String, completion: #escaping (Product)->Void) {
Database.database().reference().child("products").child(key).observeSingleEvent(of: .value) { (snap) in
if let productObject = snap.value as? [String: Any]
{
if let ref = productObject["ref"],
let price = productObject["price"],
let type = productObject["type"],
let description = productObject["description"],
let id = productObject["id"]{
let p = Product(id: id as? String, ref: ref as! String, description: description as! String, type: type as! String, price: price as! String, qtyOrdred: qty)
completion(p)
}
}
}
}
i call it like this :
override func viewWillAppear(_ animated: Bool) {
self.getData { (ps) in
print(ps)
self.tableView.reloadData()
}
}
The problem is that it always print an empty array, and my tableview data never changes
You don't return from getData completion , you need a dispatch group
let g = DispatchGroup()
func getData(completion: #escaping ([Product])->Void){
var allProduct = [Product]()
for product in orderedProduct {
g.enter()
getProductWithKey(qty: product.qty, key: product.id) { (p) in
print(p.ref)
allProduct.append(p)
g.leave()
}
}
g.notify(queue:.main) {
completion(allProduct)
}
}
Your getData function is returning as soon as the for loop is finished. As the call inside the loop is async there isn't any data when the loop finishes.
Instead of reloading the table just insert rows as they arrive.
for product in orderedProduct {
getProductWithKey(qty: product.qty, key: product.id) { [weak self] (p) in
guard let self = self else { return }
allProduct.append(p)
guard let index = allProduct.firstIndex(of: p) else { return }
self.tableView.insertRow(at: IndexPath(row: index, section: 0))
}
}

Resources