Nested struct for UITableView Swift/JSON - json

I need to display topic and description in a UITableView.
struct Topics: Decodable {
let subtopics: [subTopic]
struct subTopic: Decodable {
var topic = ""
var description = ""
}
}
The data for the TableView is fetched with a URL post request and assigned inside a do/try/catch block.
let Topics = Topics()
excerpt from URLRequest:
self.Topics = try JSONDecoder().decode(Topics.self, from: data)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "topicCell") as! topicCell
var subTopic = Topics?.subtopics[indexPath.row]
cell.topicLabel?.text = subTopic.topic
cell.descriptionTextView?.text = subTopic.description
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Topics?.subtopics.count
}
Sample JSON:
{"subtopics":[{"topic":"Computer Science", "description":"beginner course"},{"topic":"Data Analysis", "description":"beginner course"}]}

You give the the variable the same name as the struct.
let Topics = Topics()
Don't do that. It is confusing and can cause unexpected behavior.
There is a reason for the naming convention to name variables lowercase and structs/classes uppercase.
For less confusion name the top object different for example
struct Response: Decodable {
let subtopics: [SubTopic]
struct SubTopic: Decodable {
let topic, description : String
}
}
The default values (and variables) in SubTopic make no sense.
My next recommendation is to omit the top object and declare the data source array
var subTopics = [Response.SubTopic]()
and assign
let response = try JSONDecoder().decode(Response.self, from: data)
self.subTopics = response.subtopics
This will clean up the table view code and gets rid of the ugly optionals
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "topicCell") as! topicCell
let subTopic = subTopics[indexPath.row]
cell.topicLabel?.text = subTopic.topic
cell.descriptionTextView?.text = subTopic.description
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return subTopics.count
}

Related

fetch json data to tableview in swift 5

I am trying to fetch data from php file in json to my tableview in swift 5
I am able to get the data in the console as json array,
what I want is to fetch the data into my custom cell but I am not getting the json data array keys for example data["user_id"] it is not loading anything
my json data one array sample
func getUsers() {
AF.request(SiteUrl).validate().responseJSON { response in
switch response.result {
case .success:
print("Validation Successful)")
if let json = response.data {
do{
let data = try JSON(data: json)
let str = data
print("DATA PARSED: \(str)")
}
catch{
print("JSON Error")
}
}
case .failure(let error):
print(error)
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let customCell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier, for: indexPath) as! TableViewCell
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
cell.textLabel?.text = "Hi Hadi"
//customCell.AdTitle.text =
return customCell
}
First of all it's highly recommended to drop SwiftyJSON in favor of Codable.
Nevertheless just create a data source array, assign the received data to the data source array and reload the table view. In cellForRow get the item from the array and assign the values to the corresponding UI elements
var data = [JSON]()
func getUsers() {
AF.request(SiteUrl).validate().responseJSON { response in
switch response.result {
case .success:
print("Validation Successful)")
if let json = response.data {
do{
let jsonData = try JSON(data: json)
self.data = jsonData.arrayValue
self.tableView.reloadData() // we are already on the main thread
print("DATA PARSED: \(jsonData)")
}
catch {
print("JSON Error", error)
}
}
case .failure(let error):
print(error)
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let customCell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier, for: indexPath) as! TableViewCell
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
let item = data[indexPath.row]
cell.textLabel?.text = item["user_id"].string
return cell
}
And this is the modern way, first create a struct (you have to add the other members according to the JSON and use camelCase names: "user_id" -> userId)
struct User : Decodable {
let id, contactName : String
let userId : String
}
and replace the other code above with
var users = [User]()
func getUsers() {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
AF.request(SiteUrl).validate().responseDecodable(decoder: decoder) { (response : DataResponse<[User],AFError>) in
switch response.result {
case .success(let result):
print("Validation Successful)")
self.users = result
self.tableView.reloadData()
case .failure(let error): print("JSON Error", error)
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let customCell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier, for: indexPath) as! TableViewCell
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath)
let user = users[indexPath.row]
cell.textLabel?.text = user.userId
return cell
}

Trying to reference each item of custom type for tableView cells

I am parsing data from a JSON file and I've created a struct to hold that data. I am trying to display each item of the custom struct in a tableView, but I'm getting stuck on how I should reference each item.
Here's my struct:
struct Country: Codable {
var id: Int
var country: String
var capital: String
var nationalLanguage: [String]
var population: Int
}
And here is my table view controller. Right now I only know how to reference a single item in my custom type. This obviously sets all of the cells to that one item.
class TableViewController: UITableViewController {
var countryItem: Country?
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cells", for: indexPath)
if let country = countryItem {
cell.textLabel!.text = String(country.population)
}
return cell
}
}
When I print out my countryItem variable, this is what I get:
Country(id: 1, country: "United States", capital: "Washington D.C.", nationalLanguage: ["English"], population: 328239523)
Do I need to somehow set that as an array so I can refer to each item individually?
UPDATED:
Option1
class TableViewController: UITableViewController {
var countryItem: Country?
var arrayStrings: [String] {
guard let countryItem = countryItem else { return [] }
return [
"\(countryItem.id)",
countryItem.country,
countryItem.capital,
countryItem.nationalLanguage.joined(separator: ", "),
"\(countryItem.population)",
]
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayStrings.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cells", for: indexPath)
cell.textLabel!.text = arrayStrings[indexPath.row]
return cell
}
}
Option2
class TableViewController: UITableViewController {
var countryItem: Country?
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return countryItem == nil ? 0 : 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cells", for: indexPath)
let text: String
switch indexPath.row {
case 0:
text = "\(countryItem!.id)"
case 1:
text = countryItem!.country
case 2:
text = countryItem!.capital
case 3:
text = countryItem!.nationalLanguage.joined(separator: ", ")
case 4:
text = "\(countryItem!.population)"
default:
break
}
cell.textLabel!.text = text
return cell
}
}
If you all you want is just display string representation of all fields in your struct, then yes, convert them into a single array of string and use reusable cells to render.
If you need different styles for each field, then you may not need an array, just create some custom cells and then assign data for them from the struct.
Or maybe you don't even need a table view here since I see you have only one Country and no reusable needed here. Just create a custom view with 5 (maybe) labels and display your data.
For some reason, you still need a table view, then create a single cell that can display all information for your country.
Hope it can help you.
class TableViewController: UITableViewController {
var countryItem = [Country]()
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return countryItem.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cells", for: indexPath)
cell.textLabel!.text = countryItem[indexPath.row].country
return cell
}
}

"Value of type [Shelter] has not member 'name'"

I am trying to parse an endpoint for some JSON in Swift and use the name member as a title of my cell. I have created a struct, which conforms to the data offered by the endpoint. However, when trying to use it as my cell name, I get the error Value of type [Shelter] has not member 'name'.
Some code snippets:
This is my defined structs:
Shelters.swift:
struct Shelters: Codable {
var objects: [Shelter]
}
Shelter.swift:
struct Shelter: Codable {
var name: String
var shortdescription: String
var lastedited: String
}
Finally, this is from my ViewController.
var shelters = [Shelter]()
override func viewDidLoad() {
super.viewDidLoad()
performSelector(inBackground: #selector(backgroundProc), with: nil)
}
#objc func backgroundProc() {
let shelterUrl = "https://192.168.1.10/api/shelters/?format=json"
if let url = URL(string: shelterUrl) {
if let data = try? Data(contentsOf: url) {
parse(json: data)
}
}
}
//JSON Parser
func parse(json: Data) {
let decoder = JSONDecoder()
if let jsonShelters = try? decoder.decode(Shelters.self, from: json) {
shelters = jsonShelters.objects
}
}
This is where the code fails:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return shelters.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Shelters", for: indexPath)
cell.textLabel?.text = shelters.name[indexPath.row] //It fails right here. With the error: Value of type '[Shelter]' has no member 'name'
cell.detailTextLabel?.text = "Shelter"
return cell
}
Use
cell.textLabel?.text = shelters[indexPath.row].name
Value of type [Shelter] has not member 'name'
If you look closely on the error description, it's telling you that a variable of Type: [Shelter] does not have an attribute named name. In other words, <#Obj#>.name is a property of Shelter and NOT [Shelter].
So you need to reference an object instead of an array on the failing line, using: shelters[indexPath.row], and then you can access shelters[indexPath.row].name.
Your line should be:
cell.textLabel?.text = shelters[indexPath.row].name

Storing String and Int in a dictionary in swift

I'm almost new to Swift. In this URL I'll get some element; one of elements is categoryList which has two elements itself. I set the goodTypeName as the table's cell title, and when a cell is pressed it needs to send the goodType which is number (Int) to be placed in the next Url. I tried to create a dictionary but I failed!
UiTable code :::
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Global.GlobalVariable.names.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if Global.GlobalVariable.names == []
{
self.DisplayMessage(UserMessage: "nothing is available ")
print("server is nil")
}
let cell = UITableViewCell()
let content = Global.GlobalVariable.names[indexPath.row]
cell.textLabel?.text = content
cell.accessoryType = .disclosureIndicator
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print(indexPath.item)
let next = self.storyboard?.instantiateViewController(withIdentifier: "SVC")
self.navigationController?.pushViewController(next!, animated: true)
}
My problem is not with populating them in a table, my problem is when a cell is selected , goodtype is needed to be sent to next page, becuase next page's url has to have the goodtype code.
You can use the "prepareSegue" to pass Data.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "something"{
if let startViewController = segue.destination as? StartViewController{
startViewController.goodtype = Global.GlobalVariable.goodtype[indexPath.row]
}
}
}
And in your StartViewController just assign a variable to receive your data :
var goodtype = String()
Or use the navigation controller but with this line you can access to the another view controller property.
if let startViewController = storyboard.instantiateViewController(withIdentifier: "StartViewController") as? StartViewController {
startViewController.goodtype = Global.GlobalVariable.goodtype[indexPath.row]
let navigationController = UINavigationController()
navigationController.pushViewController(startViewController, animated: true)
}

TableView Json Array

{
"YASSINIRLA" : "True",
"KARBON" : "98",
"GRUP" : "Orta Boy",
"INDIRIMORANI" : "0",
"KLIMA_TR" : "Var",
"KREDI_FIYAT" : "107",
"SIRANO" : "1",
"YAKIT_EN" : "Diesel",
}
Hi. I solved the problem but it is too long. I created an array for all of them. I also showed it in table view. How can I make it shorter? Sorry for my English. Thanks.
dizi1.append(arac["YASSINIRLA"].string!)
dizi2.append(arac["KARBON"].string!)
dizi3.append(arac["INDIRIMORANI"].string!)
dizi4.append(arac["KLIMA_TR"].string!)
dizi5.append(arac["KREDI_FIYAT"].string!)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return diziAracModelii.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "sonAracListelee") as! sonAracListeleViewCell
cell.lblAracAdi1.text = dizi1[indexPath.row]
cell.lblAracAdi2.text = dizi2[indexPath.row]
cell.lblAracAdi3.text = dizi3[indexPath.row]
cell.lblAracAdi4.text = dizi4[indexPath.row]
cell.lblAracAdi5.text = dizi5[indexPath.row]
return cell
}
An easier thing to do is to make a struct model, where you declare the variables. Then you can make an initiator function where you initiates the variables with the data from the json object. I recommend using SwiftyJSON while doing this.
If let in the init function make sure to check if the object contains any value.
struct CustomModel {
public private(set) var yassinirla: String?
public private(set) var karbon: String?
public private(set) var indirimorani: String?
public private(set) var klimaTr: String?
public private(set) var krediFiyat: String?
init(arac: JSON) {
if let yassinirla = arac["YASSINIRLA"].string {
self.yassinirla = yassinirla
}
if let karbon = arac["KARBON"].string {
self.karbon = karbon
}
if let indirimorani = arac["INDIRIMORANI"].string {
self.indirimorani = indirimorani
}
if let klimaTr = arac["KLIMA_TR"].string else {
self.klimaTr = klima_tr
}
if let krediFiyat = arac["KREDI_FIYAT"].string else {
self.krediFiyat = krediFiyat
}
}
}
In the header you can declare your array diziAracModelii as [CustomModel] so you don't have to use separate arrays for each string. That's just bad practice. You can handle this with a single array.
diziAracModelii: [CustomModel] = [CustomModel]()
func grabJson() {
//...Download the json into a json constant
//Send in the jsonobject in the CustomModel
let newObject = CustomModel(arac: json)
//Append to your string and make sure to reload the tableView data.
diziAracModelii.append(newObject)
self.yourTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return diziAracModelii.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "sonAracListelee") as sonAracListeleViewCell else { return UITableViewCell() }
cell.lblAracAdi1.text = diziAracModelii[indexPath.row].yassinirla
cell.lblAracAdi2.text = diziAracModelii[indexPath.row].karbon
cell.lblAracAdi3.text = diziAracModelii[indexPath.row].indirimorani
cell.lblAracAdi4.text = diziAracModelii[indexPath.row].klimaTr
cell.lblAracAdi5.text = diziAracModelii[indexPath.row].krediFiyat
return cell
}