Swift Tableview data load getting Index out of range - json

My scenario, I am trying to load JSON data into Tableview. Here, Tableview custom cell I am maintaining. Whenever trying to upload data into tableview, I may have chance to add new data from server.
Here, while loading I am getting Index out of range error. Every time I am calling JSON function from viewWillAppear. I have enough data, Inside my array nothing uneven data. Below code I am using
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated) // No need for semicolon
self.tableArray.removeAll()
self.cartname.removeAll()
self.parentid.removeAll()
self.name.removeAll()
self.year.removeAll()
parseJSON()
}
This is my JSON Process
if let content = json["content"] as? [[String:String]] {
print(json)
for category in content {
let cat_id = category["cat_id"]
let cat_name = category["cat_name"]
let cat_parentid = category["cat_parentid"]
let name = category["name"]
let year = category["year"]
self.tableArray.append(cat_id ?? "unknnown")
self.cartname.append(cat_name ?? "unknnown")
self.parentid.append(cat_parentid ?? "unknnown")
self.name.append(name ?? "unknnown")
self.year.append(year ?? "unknnown")
}
Tableview cell data load
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyCustomCell
cell.cat_id.text = self.tableArray[indexPath.row]
cell.cat_name.text = self.cartname[indexPath.row]// Some time Here I am getting out of range error
cell.cat_parentid.text = self.parentid[indexPath.row]
cell.name.text = self.name[indexPath.row] // Here I am getting out of range error
cell.year.text = self.year[indexPath.row]
return cell
}
}

It's expected with multiple arrays when using the tableView ,First from OOP point you need to create 1 model like
struct Root: Codable {
let catID, catName, catParentid, year,name: String?
enum CodingKeys: String, CodingKey {
case catID = "cat_id"
case catName = "cat_name"
case catParentid = "cat_parentid"
case year, name
}
}
and use Codable to parse the json
var arr = [Root]()
do {
let content = json["content"] as! [[String:String]]
let staData = try JSONSerialization.data(withJSONObject:content,options:[])
arr = try JSONDecoder().decode([Root].self, from:staData)
}
catch {
print(error)
}
in numberOfRows
return arr.count
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyCustomCell
let item = arr[indexPAth.row]
cell.cat_id.text = item.catID
cell.cat_name.text = item.catName
cell.cat_parentid.text = item.catParentid
cell.name.text = item.name
cell.year.text = item.year
return cell
}

Related

"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

UITableView with Sections as Date from Json Data

I am working on table view to render some data received after JSON parsing. I want my table view to have sections based on different dates. Each record in JSON is an event and multiple events can take place on single date.
Here is my JSON data
https://get.rosterbuster.com/wp-content/uploads/dummy-response.json
I want to render my table view like this
Table View with Sections as Date
What I have done sofar:
I have parsed the data in following Structure
struct Roster : Codable {
let flightnr: String?
let date: String?
let aircraftType: String?
let tail: String?
let departure: String?
let destination: String?
let departTime: String?
let arrivalTime: String?
let dutyID: String?
let dutyCode: String?
let captain: String?
let firstOfficer: String?
let flightAttendant: String?
enum CodingKeys: String, CodingKey {
case flightnr = "Flightnr"
case date = "Date"
case aircraftType = "Aircraft Type"
case tail = "Tail"
case departure = "Departure"
case destination = "Destination"
case departTime = "Time_Depart"
case arrivalTime = "Time_Arrive"
case dutyID = "DutyID"
case dutyCode = "DutyCode"
case captain = "Captain"
case firstOfficer = "First Officer"
case flightAttendant = "Flight Attendant"
}
}
I have also setup basic table view but don't know how to group the retrieved data into different sections as per the image I have attached above.
Any help would be appreciated.
This is the approach I'd suggest:
1) get number of sections by mapping the API JSON response in a set based on the date property. Here's something you could use (maybe you don't need to cast it in Array as well and you want to check if date is not nil)
self.sections = Array(Set(self.dataModel.map({ (roster) -> String in
roster.date!
})))
2) set your rowsPerSection data model by creating an array of Roster for each section.
//first set the array of sections.count dimension and empty array for each item
self.sections.forEach({ (string) in
self.rowsPerSection.append([])
})
//then set each array
for index in 0..<self.sections.count {
self.dataModel.forEach({ (roster) in
if roster.date == self.sections[index] {
self.rowsPerSection[index].append(roster)
}
})
}
This is my dummy code, I tested it with your URL and it works:
class ViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var dataModel = [Roster]()
var sections = [String]()
var rowsPerSection = [[Roster]]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
APICall { (rosters) in
DispatchQueue.main.async {
self.dataModel = rosters!
self.sections = Array(Set(self.dataModel.map({ (roster) -> String in
roster.date!
})))
//first set the array of sections.count dimension and empty array for each item
self.sections.forEach({ (string) in
self.rowsPerSection.append([])
})
//then set each array
for index in 0..<self.sections.count {
self.dataModel.forEach({ (roster) in
if roster.date == self.sections[index] {
self.rowsPerSection[index].append(roster)
}
})
}
self.tableView.reloadData()
}
}
}
func APICall(onSuccess: #escaping(_ response: [Roster]?) -> Void) {
let group = DispatchGroup()
group.enter()
DispatchQueue.global(qos: .default).async {
let url = URL(string: "https://get.rosterbuster.com/wp-content/uploads/dummy-response.json")!
let requestURL = URLRequest(url: url)
let session = URLSession.shared
session.dataTask(with: requestURL) { (data, response, error) in
let decoder = JSONDecoder()
let responseJson = try! decoder.decode([Roster].self, from: data!)
onSuccess(responseJson)
group.leave()
}.resume()
group.wait()
return
}
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
for index in 0..<sections.count {
if index == section {
return rowsPerSection[index].count
}
}
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = rowsPerSection[indexPath.section] [indexPath.row].destination
return cell
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section]
}
}
Here's the screenshot -> screenshot

JSON data not passing to tableView Cell

I am trying to get the below code to pass JSON data to the table viewCell. I have confirmed that the JSON data is being captured and stored in the variable downloadLenderRates. But I cannot get the values to pass to the tabelView Cell. I confirmed that the cell identifier is named correctly and the swift file that helps manage the tableView cell is named correctly. At this point, I get no error messages and just a blank table when I run the app. I am not sure why!
class MortgageRatesVC: UIViewController, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
let mortgousURL = URL(string:"http://mortgous.com/JSON/currentRatesJSON.php")!
var lenderRates = [LenderRate]()
override func viewDidLoad() {
super.viewDidLoad()
downloadJason()
}
func downloadJason () {
lenderRates = []
// guard let downloadURL = url else { return }
URLSession.shared.dataTask(with: mortgousURL) { data, urlResponse, error in
guard let data = data else { return }
do {
let dateFormat = DateFormatter()
dateFormat.locale = Locale(identifier: "en_US_POSIX")
dateFormat.dateFormat = "yyyy-MM-dd"
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .formatted(dateFormat)
let downloadLenderRates = try decoder.decode([LenderRate].self, from: data)
// print(downloadLenderRates)
self.lenderRates = downloadLenderRates
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print(error)
}
}.resume()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return lenderRates.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "LenderCell") as? LenderCell else { return UITableViewCell() }
cell.lenderNamelbl.text = lenderRates[indexPath.row].financialInstitution
print(lenderRates[indexPath.row].financialInstitution)
return cell
}
}
The syntax
guard let cell = tableView.dequeueReusableCell(withIdentifier: "LenderCell") as? LenderCell else {
return UITableViewCell()
}
is very bad habit.
The guard can only fail if there is a design error which occurs for example if the developer forgot to set the class of the cell to the custom class. In this case you won't see anything in the table view.
This is one of the few cases where force-unwrapping is recommended. If the design is set up properly the cell is valid and its type is the custom class. Further use always the API dequeueReusableCell(withIdentifier:for:) which returns a non-optional cell.
let cell = tableView.dequeueReusableCell(withIdentifier: "LenderCell", for: indexPath) as! LenderCell

How do I populate a tableview with JSON data from Alamofire?

Before I state my problem, I want to let everyone know that I am new to the coding environment that is Swift, so forgive me for my lack of knowledge. Currently, I am having trouble populating the cells of a tableview using Alamofire based on the data that is returned from a JSON URL. When I run the app in a simulator, data is displayed in the console, but the app crashes with a SIGABRT error. For reference, instead of using a viewcontroller with a tableview element inside, I am using a tableviewcontroller. Here is my code thus far:
import UIKit
import Alamofire
class TableViewController: UITableViewController {
var responseArray: NSArray = []
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request("https://rss.itunes.apple.com/api/v1/us/apple-music/top-songs/all/10/explicit.json").responseJSON { response in
if let json = response.result.value {
print(json)
self.responseArray = json as! NSArray
}
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return responseArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "top10", for: indexPath)
// Configure the cell...
let whichSong = responseArray[(indexPath as NSIndexPath).row]
let artistName = (whichSong as AnyObject)["artistName"] as? String
cell.textLabel?.text = artistName
return cell
}
The crash occurs because the root object of the JSON is a dictionary (represented by {}) not an array.
First of all declare a type alias for a JSON dictionary and the data source array as native type, an array of JSON dictionaries:
typealias JSONDictionary = [String:Any]
var responseArray = [JSONDictionary]()
Then parse the JSON and reload the table view, you want probably the array for key results:
Alamofire.request("https://rss.itunes.apple.com/api/v1/us/apple-music/top-songs/all/10/explicit.json").responseJSON { response in
if let json = response.result.value as? JSONDictionary,
let feed = json["feed"] as? JSONDictionary,
let results = feed["results"] as? [JSONDictionary] {
print(results)
self.responseArray = results
self.tableView.reloadData()
}
}
Then show the data in cellForRow
let song = responseArray[indexPath.row]
cell.textLabel?.text = song["artistName"] as? String
Okay so firstly change
let cell = tableView.dequeueReusableCell(withIdentifier: "top10", for: indexPath)
to
let cell = tableView.dequeueReusableCell(withIdentifier: "top10")
However, with this, cell will be cell?, you will have to return cell!.
Next in your Alamofire response,
if let json = response.result.value {
print(json)
self.responseArray = json as! NSArray
self.reloadData()
//If above line doesn't work, try tableView.reloadData()
}
Why?
The Alamofire request is "asynchronous", meaning it executes codes while your app is doing other things. Therefor, it is likely that you are setting that array after your table is loaded, hence the reloadData()
Replace the below line
let cell = tableView.dequeueReusableCell(withIdentifier: "top10", for: indexPath)
with
let cell = tableView.dequeueReusableCell(withIdentifier: "top10")

Problems with adding value to an array with array.append() in Swift

in the moment we're programming a Swift App for iOS in which we want to get data of our JSON Website (MySql database) into the TableViewCell. The problem is by appending the text values of the strings for the label in the cell. Swift can import the JSON values into the name variable but I cant assign it to the text array for the cells. I havent no syntax errors, but the data[0] Variable print sth. as "123". Why it is 123? The test Value is "Test". I don't now where the problem by appending the value to the array is, that the result is 123 after that. Please help.
Here is the sourcecode:
class listViewViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var data:[String?] = []
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let myUrl = URL(string: "");//Empty link for this question
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"
let postString = "lid=1";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil {
print("error=\(error)")
return
}
print("response = \(response!)")
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let Name = parseJSON["Name"] as? String
print("\(Name)")//Test
self.data.append(Name!)
print("\(data![0])" as String)//123
}
} catch {
print(error)
}
}
task.resume()
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! listViewTableViewCell
print("\(data[indexPath.row])")
let dataCell = data[indexPath.row]
cell.listViewCell.text = dataCell
return cell
}
}
this because your array properties and data callback block parameter have the same name "data". in your code you user print("(data![0])" as String) instead of print("(self.data![0])" as String) => you have to add self.
then you can optimise your code like this (it's optional : it's just like a code review ;) )
try to do this
- change your array type to String like this
var data = [String]()
- webService callback change your code like this :
if let parseJSON = json {
if let Name = parseJSON["Name"] as? String{
print("\(Name)")
self.data.append(Name)
print("\(self.data.last)")//123
}
}
When you append to your data array you use self.data but you then print from data which is the parameter to the inner function. You add and print from different arrays.