Reputation: 620
I'm new to iOS and making an ExpandableTableView with JSON data. The data is in form of nested arrays. I want to set data of parent array as TableView header and data of child array as ExpandableTable elements. I'm performing JSONDecoding through struct and enums and storing the response in DataModelClass. While Decoding it's throwing error
typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [], debugDescription: "Expected to decode Dictionary<String, Any> but found an array instead.", underlyingError: nil))
that is ok because I know that I'm missing something very small. I've gone through it many times but I'm unable to understand the problem.
There are many similar types of questions on stack but none of then are matching my criteria. Please help me out.
My JSON is
[
{
"category_id": "1",
"category_name": "ROLL",
"category_start_time": "10:00:00",
"category_end_time": "22:59:59",
"data": [
{
"id": "301",
"item_code": null,
"item_name": "CHICKN ROLL",
"main_item_id": "1",
"item_price": null,
"tax_id": "0",
"tax_per": "0",
"tax_amount": "0",
"item_net_price": "180",
"sub_item_price": "0",
"sub_tax_amount": "0",
"sub_item_net_price": "0",
"created_date": "2018-02-28 12:20:53",
"created_by": null,
"image_name": "",
"image_path": null,
"quantity": null,
"notify_quant": null,
"active": "1",
"today_avilable": "1",
"kot": "1",
"counter_id": "0",
"store_id": "1"
}
]
}
]
and the Model class is
import Foundation
class AllStoreItems {
var category_id: String
var category_name: String
var category_start_time: String
var category_end_time: String
var data: [data]
var expanded: Bool
init(category_id: String, category_name: String, category_start_time: String, category_end_time: String, data: [data], expanded: Bool = false) {
self.category_id = category_id
self.category_name = category_name
self.category_start_time = category_start_time
self.category_end_time = category_end_time
self.data = data
self.expanded = expanded
}
}
class ItemsData {
var id: String
var item_code: String
var item_name: String
var main_item_id: String
var item_price: String
var tax_id: String
var tax_per: String
var tax_amount: String
var item_net_price: String
var sub_item_price: String
var sub_tax_amount: String
var sub_item_net_price: String
var created_date: String
var created_by: String
var image_name: String
var image_path: String
var quantity: String
var notify_quant: String
var active: String
var today_avilable: String
var kot: String
var counter_id: String
var store_id: String
init(id: String, item_code: String, item_name: String, main_item_id: String, item_price: String, tax_id: String, tax_per: String, tax_amount: String, item_net_price: String, sub_item_price: String, sub_tax_amount: String, sub_item_net_price: String, created_date: String, created_by: String, image_name: String, image_path: String, quantity: String, notify_quant: String, active: String, today_avilable: String, kot: String, counter_id: String, store_id: String) {
self.id = id
self.item_code = item_code
self.item_name = item_name
self.main_item_id = main_item_id
self.item_price = item_price
self.tax_id = tax_id
self.tax_per = tax_per
self.tax_amount = tax_amount
self.item_net_price = item_net_price
self.sub_item_price = sub_item_price
self.sub_tax_amount = sub_tax_amount
self.sub_item_net_price = sub_item_net_price
self.created_date = created_date
self.created_by = created_by
self.image_name = image_name
self.image_path = image_path
self.quantity = quantity
self.notify_quant = notify_quant
self.active = active
self.today_avilable = today_avilable
self.kot = kot
self.counter_id = counter_id
self.store_id = store_id
}
}
My struct and enums are
import Foundation
struct AllItem: Decodable {
var category_id: String
var category_name: String
var category_start_time: String
var category_end_time: String
var data: [data]
enum AllItem: String {
case category_id = "category_id"
case category_name = "category_name"
case category_start_time = "category_start_time"
case category_end_time = "category_end_time"
case data = "data"
}
}
struct data: Decodable {
var id: String
var item_code: String
var item_name: String
var main_item_id: String
var item_price: String
var tax_id: String
var tax_per: String
var tax_amount: String
var item_net_price: String
var sub_item_price: String
var sub_tax_amount: String
var sub_item_net_price: String
var created_date: String
var created_by: String
var image_name: String
var image_path: String
var quantity: String
var notify_quant: String
var active: String
var today_avilable: String
var kot: String
var counter_id: String
var store_id: String
enum data: String {
case id = "id"
case item_code = "item_code"
case item_name = "item_name"
case main_item_id = "main_item_id"
case item_price = "item_price"
case tax_id = "tax_id"
case tax_per = "tax_per"
case tax_amount = "tax_amount"
case item_net_price = "item_net_price"
case sub_item_price = "sub_item_price"
case sub_tax_amount = "sub_tax_amount"
case sub_item_net_price = "sub_item_net_price"
case created_date = "created_date"
case created_by = "created_by"
case image_name = "image_name"
case image_path = "image_path"
case quantity = "quantity"
case notify_quant = "notify_quant"
case active = "active"
case today_avilable = "today_avilable"
case kot = "kot"
case counter_id = "counter_id"
case store_id = "store_id"
}
}
and my ViewController is
class CustomItemTableView: UITableViewCell {
@IBOutlet weak var textLabelOne: UILabel!
@IBOutlet weak var textLabelTwo: UILabel!
}
class AllItemsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, AllItemsHeaderViewDelegate {
@IBOutlet weak var headerStoreLogoIV: UIImageView!
@IBOutlet weak var tableView: UITableView!
var storeId: String = ""
var storeCatId: String = ""
var storeName: String = ""
var storeLogoLink: String = ""
@IBOutlet weak var storeImage: UIImageView!
@IBOutlet weak var storeNameLabel: UILabel!
let mainUrl = BaseURL()
var items = [AllStoreItems]()
var subItems = [ItemsData]()
override func viewDidLoad() {
super.viewDidLoad()
ExpandItemsApi()
let imgUrl: String = mainUrl.MainUrl + "logo/" + storeLogoLink
let imgUrlStr: String = imgUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
//let imgUrl2: URL = URL(string: imgUrlStr)!
//let imgUrl3 = URLRequest(url: imgUrl2!)
storeNameLabel.text = storeName
// storeNames.text = storeName
// storeCategory.text = storeCatId
// storeIds.text = storeId
// imgLink.text = storeLogoLink
storeImage.downloadedFrom(link: imgUrlStr)
}
@IBAction func backButton(_ sender: Any) {
self.dismiss(animated: false, completion: nil)
}
func ExpandItemsApi() {
let myActivityIndicator = UIActivityIndicatorView(activityIndicatorStyle: UIActivityIndicatorViewStyle.gray)
myActivityIndicator.center = view.center
myActivityIndicator.hidesWhenStopped = false
myActivityIndicator.startAnimating()
view.addSubview(myActivityIndicator)
let itemsUrl = URL(string: mainUrl.MainUrl + "viewmaincat")
var itemUrls = URLRequest(url: itemsUrl!)
itemUrls.httpMethod = "POST"
itemUrls.addValue("application/json", forHTTPHeaderField: "content-type")
itemUrls.addValue("application/json", forHTTPHeaderField: "Accept")
let storeDetails = ["store_id": storeId] as [String: String]
do {
itemUrls.httpBody = try JSONSerialization.data(withJSONObject: storeDetails, options: .prettyPrinted)
}catch let error {
toastNeck(message: "\(error)")
myActivityIndicator.stopAnimating()
myActivityIndicator.hidesWhenStopped = true
}
URLSession.shared.dataTask(with: itemUrls) {
(datas, response, error) in
if datas != nil {
do {
let itemDetails = try JSONDecoder().decode(AllItem.self, from: datas!)
self.items.append(AllStoreItems(category_id: itemDetails.category_id, category_name: itemDetails.category_name, category_start_time: itemDetails.category_start_time, category_end_time: itemDetails.category_end_time, data: itemDetails.data))
print(itemDetails.data)
// let subItem = try JSONDecoder().decode(data.self, from: datas!)
// self.subItems.append(ItemsData(id: subItem.id, item_code: subItem.item_code, item_name: subItem.item_name, main_item_id: subItem.main_item_id, item_price: subItem.item_price, tax_id: subItem.tax_id, tax_per: subItem.tax_per, tax_amount: subItem.tax_amount, item_net_price: subItem.item_net_price, sub_item_price: subItem.sub_item_price, sub_tax_amount: subItem.sub_tax_amount, sub_item_net_price: subItem.sub_item_net_price, created_date: subItem.created_date, created_by: subItem.created_by, image_name: subItem.image_name, image_path: subItem.image_path, quantity: subItem.quantity, notify_quant: subItem.notify_quant, active: subItem.active, today_avilable: subItem.today_avilable, kot: subItem.kot, counter_id: subItem.counter_id, store_id: subItem.store_id))
}catch let errors {
self.toastNeck(message: "\(errors)")
print(errors)
DispatchQueue.main.async{
myActivityIndicator.stopAnimating()
myActivityIndicator.hidesWhenStopped = true
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}.resume()
}
func numberOfSections(in tableView: UITableView) -> Int {
return items.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items[section].data.count
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 50
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if items[indexPath.section].expanded {
return 50
}else {
return 0
}
}
func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return 5
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = AllItemsHeaderView()
header.customInit(title: items[section].category_name, section: section, delegate: self)
return header
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CustomItemTableView = tableView.dequeueReusableCell(withIdentifier: "customItems") as! CustomItemTableView
cell.textLabelOne.text = items[indexPath.section].data[indexPath.row].item_name
cell.textLabelTwo.text = items[indexPath.section].data[indexPath.row].item_price
return cell
}
func toggleSection(header: AllItemsHeaderView, section: Int) {
items[section].expanded = !items[section].expanded
tableView.beginUpdates()
for i in 0 ..< items[section].data.count {
tableView.reloadRows(at: [IndexPath(row: i, section: section)], with: .automatic)
}
tableView.endUpdates()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
toastNeck(message: items[indexPath.section].data[indexPath.row].item_name)
}
func toastNeck(message: String) {
DispatchQueue.main.async {
let toastLabel = UILabel(frame: CGRect(x: self.view.frame.size.width/2-100, y: self.view.frame.size.height/2, width: 200, height: 40))
toastLabel.backgroundColor = UIColor.black.withAlphaComponent(0.4)
toastLabel.textColor = UIColor.white
toastLabel.textAlignment = NSTextAlignment.center
toastLabel.text = message
toastLabel.layer.cornerRadius = 15
toastLabel.clipsToBounds = true
self.view.addSubview(toastLabel)
UIView.animate(withDuration: 8, animations: {
toastLabel.alpha = 0}, completion: {(isCompleted) in toastLabel.removeFromSuperview()})
}
}
}
if anyone need anything more, please comment...
Upvotes: 1
Views: 610
Reputation: 2902
Try changing these two lines:
let itemDetails = try JSONDecoder().decode(AllItem.self, from: datas!)
self.items.append(AllStoreItems(category_id: itemDetails.category_id, category_name: itemDetails.category_name, category_start_time: itemDetails.category_start_time, category_end_time: itemDetails.category_end_time, data: itemDetails.data))
to
let itemDetails = try JSONDecoder().decode([AllItem].self, from: datas!)
for item in itemDetails {
self.items.append(AllStoreItems(category_id: item.category_id, category_name: item.category_name, category_start_time: item.category_start_time, category_end_time: item.category_end_time, data: item.data))
}
And also there is no need of the enums AllItem
and data
as the property names are already same as JSON keys.
Upvotes: 1