I'm trying to parse API JSON and display it in UITableView, but the problem is I cannot access all array in this API.
struct RootResults: Codable {
var results: [results]
}
// MARK: - results
struct results: Codable {
var container_number: String?
var commodities: [commodities]
}
// MARK: - commodities
struct commodities: Codable {
var commodity_name_en: String?
var varieties: [varieties]
}
// MARK: - varieties
struct varieties: Codable {
var variety_name_en: String?
var variety_name_ar: String?
}
import UIKit
class ViewController: UIViewController {
#IBOutlet var resultsTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
abuseedoAPIget()
}
var arrData = [results]()
var arrResults = [commodities]()
func abuseedoAPIget(){
let urlJSON = "http://abuseedotrading.com/apps/api/acp/?key=4ea1e08dd9ab329bbdaa9e5b42939c04&query=list_containers"
guard let url = URL(string: urlJSON) else {return}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {return}
guard error == nil else {return}
do {
let decoder = JSONDecoder()
let APIResponse = try decoder.decode(RootResults.self, from: data)
self.arrData = APIResponse.results
DispatchQueue.main.async{
self.resultsTable.reloadData()
}
} catch let error {
print("Failed to decode JSON:", error)
}
}.resume()
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
let dataa = arrData[indexPath.row]
cell.conLabel.text = dataa.container_number
cell.comLabel.text = dataa.commodity_name_en
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableView.automaticDimension
}
}
For Swift to be able to decode the JSON response into objects you need to have a similar structure defined in Swift using objects.
Your RootResults object needs to implement Codable protocol and represent the JSON structure.
Below a part of the returned JSON:
{
"status": 200,
"runtime": 1.7315270900726,
"results_count": 13,
"results": [
{
"container_id": 36473,
"container_number": "MMAU1163814",
"shipment_id": 17359,
}
}
RootResults would look something like this:
struct RootResults: Codable {
let status: Int
let runtime: Float
let results_count: 13
let results: [Container]
}
struct Container: Codable {
let container_id: Int
let container_number: String
let shipment_id: Int
}
More information on Swift Codable
Swift codable
SO question about
In the function tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) you are accessing data on different levels.
The container_number -property is accessible when retrieving an object from results. The commodity_name_en is a "level" deeper and part-of the commodities -array. To access the first item in the commodities -array , try the following:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as! TableViewCell
let dataa = arrData[indexPath.row]
cell.conLabel.text = dataa.container_number
cell.comLabel.text = dataa.commodities[0].commodity_name_en
return cell
}
As Vadian mentions, it's common to start types(struct, class, enum) with a capital letter in Swift. Have a look at struct and classes documentation
Related
I already have the array that is printed on the console after I decode it from JSON data. However, the table delegate are not working.
I tried using static array that is created on viewController and its working , but when I use the JSON data it wont work anymore. here is my table view protocols below.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return placeModel.indexCount
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let cell = UITableViewCell(style: .default, reuseIdentifier: "restoCell")
let cell = tableView.dequeueReusableCell(withIdentifier: "restoCell", for: indexPath)
let array = placeModel.name[indexPath.row]
cell.textLabel?.text = String (array)
return cell
}
Here is whats on my ViewController , take note that the PlacesModel is at another swift file. the DispatchQueue.main.async is placed at didUpdateRestoLocation function. i tried place the delegate but still not working
class ViewController: UIViewController, CLLocationManagerDelegate {
var locationManager = CLLocationManager()
var placesManager = PlacesManager()
var placesClient = GMSPlacesClient()
var placeModel = PlacesModel(name: [], placesID: [], indexCount: 0)
#IBOutlet weak var cityText: UILabel!
#IBOutlet weak var kahitSaan: UILabel!
#IBOutlet weak var tableViewCafe: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
//ask location authorization
locationManager.requestWhenInUseAuthorization()
locationManager.requestAlwaysAuthorization()
placesManager.delegate = self
locationManager.delegate = self
tableViewCafe.delegate = self
tableViewCafe.dataSource = self
}
//MARK: - gps button
#IBAction func button(_ sender: Any) {
locationManager.startUpdatingLocation()
}
}
//MARK: - location manager delegate
extension ViewController {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.last {
let lat = location.coordinate.latitude
let lon = location.coordinate.longitude
placesManager.fetchCafes( latitude: lat , longitude: lon)
}
locationManager.stopUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print(error)
}
}
//MARK: - PLACESMANAGER - DELEGATE
extension ViewController : PlacesManagerDelegate {
func didUpdateRestoLocation(_ placeManager: PlacesManager, placesModel: PlacesModel) {
DispatchQueue.main.async {
self.kahitSaan.text = "\(placesModel.name[0])"
self.tableViewCafe.reloadData()
}
}
func didFailWithErrorProblem(error: Error) {
print(error)
}
//MARK: - table view populate
extension ViewController : UITableViewDelegate , UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//indexCount is the number of index of array from the data.
return placeModel.indexCount
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let cell = UITableViewCell(style: .default, reuseIdentifier: "restoCell")
let cell = tableView.dequeueReusableCell(withIdentifier: "restoCell", for: indexPath)
let array = placeModel.name[indexPath.row]
cell.textLabel?.text = String (array)
return cell
}
}
my swift file PlaceModel.swift is at below. this is where the json data is placed after is was parsed.
struct PlacesModel {
let name : [String]
let placesID : [String]
let indexCount : Int
}
> here is the array of restaurant names
`["BJ\'s Restaurant & Brewhouse", "Fish\'s Wild", "Starbucks", "Pho Hoa Noodle Soup - Homestead", "Yayoi Cupertino", "Chipotle Mexican Grill", "Gochi Cupertino", "212 New York Pizza", "Oakmont Sandwiches", "Via Mia Pizza", "Mr. Ma’s Kitchen", "Bento Corner", "Duan Chun Zhen Noodle House", "Lei Garden", "Parkview Kitchen & Spirits", "Homestead Bowl & The X Bar", "Lee\'s Sandwiches", "TLT & Grill Soup", "Taiwan Porridge", "Taste Good Beijing Cuisine- Cupertino"]`
I have a model Users and in that model
static func getUsers() -> Single<[Users]> {
let path = API.Path.users.details
let params: [String: Any] = [
"utcOffset": TimeZone.current.utcOffset
]
return API.requestMany(path: path, method: .get, parameters: params)
}
i declare it like this
let sus = [Users]()
And in my cellForRowAt i have this
Users
.getUsers()
.do(onError: { [weak self] error in
print(error.localizedDescription)
})
.do(onSuccess: { [weak self] userNames in
print(userNames)
cell.name.text = userNames[indexPath.row].name
})
.subscribe()
.disposed(by: rx.disposeBag)
That code is not being called so my tableview is empty but when i tried putting it in ViewDidLoad its being called and triggering my print statement print(userNames)
Your code isn't being called because nothing is calling the function that you put the code in. It works in viewDidLoad because that function is being called.
Based on your description I would expect code that looks like this in your viewDidLoad()
Users.getUsers()
.asObservable()
.bind(to: tableView.rx.items) { tableView, index, user in
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: IndexPath(index: index)) as! MyTableViewCell
cell.configure(for: user)
return cell
}
.disposed(by: disposeBag)
Based on your comments, I've come to understand that you only want to use Rx for your network calls. In that case, the solution should look like this:
final class ExampleViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
private var users: [User] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
_ = User.getUsers()
.subscribe(
onSuccess: { [weak self] in
self?.users = $0
self?.tableView.reloadData()
},
onError: { error in
print("handle network error here")
}
)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 44
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! MyTableViewCell
cell.configure(for: users[indexPath.row])
return cell
}
}
final class MyTableViewCell: UITableViewCell {
func configure(for user: User) {
print("configure your cell here")
}
}
struct User {
static func getUsers() -> Single<[User]> { fatalError("Implement me") }
}
here is my model class I can access the TotalMeetings and FriendsDetail but unable to access MeetingsDetail
struct TotalMeetings: Decodable {
var status: Int
var message: String
var friends: [FriendDetail]?
}
struct FriendDetail: Decodable {
var id: Int
var meetings: [MeetingsDetail]
}
struct MeetingsDetail: Decodable {
var meeting_id: Int
var meeting_with: String
}
This is how am parsing my response and can get successfully the value of FriendDetail but how do I get the values e.g. meeting_id from the MeetingsDetail
var friendsArr = [FriendDetail]()
meetingsData = try decoder.decode(TotalMeetings.self, from: response.data!)
friendsArr = meetingsData.friends
This is how im populating the tableview
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return friendsArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! FriendsMeetingTVC
cell.emailLbl.text = friendsArr[indexPath.row].email //successful
// let meetingId = friendsArr[indexPath.row].meetings[indexPath.row].id // im trying this way but it show an error stating index out of range
// cell.userIdLbl.text = String(meetingId)
return cell
}
This is the API response which I have parsed
{
"status": 0,
"message": "Friends found.",
"friends": [
{
"id": 52,
"meetings": [
{
"id": 47,
"meeting_with": "Bbb"
}
]
}
]
}
The model class
struct TotalMeetings: Decodable {
var status: Int
var message: String
var friends: [FriendDetail]?
}
struct FriendDetail: Decodable {
var id: Int
var meetings: [MeetingsDetail]
}
struct MeetingsDetail: Decodable {
var id: Int
var meeting_with: String
}
Im calling the API here and the call is successful.
var meetingssData :Friends!
let decoder = JSONDecoder()
do{
meetingsData = try decoder.decode(TotalMeetings.self, from: response.data!)
let meet = [self.meetingsData!.friends].compactMap({$0}).flatMap({$0})
print(meetingsData!)
}catch{
print(error)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! FriendsMeetingTVC
return cell
}
Please guide me how do I populate the tableview with the response coming from API call.
you have to declared array instance of type according to your need either FriendDetail or MeetingsDetail.
After fetching data from api parse it and store mapped data into an array instance. Then, reload table view.
numberOfRowsInSection method of tableview returns number of rows using your array count.
You can access value by using dot notation as shown in "cellForRowAt indexPath:" method.
class MeetingVC: UIViewController, UITableViewDelegate, UITableViewDatasource {
var friendsArr = [FriendDetail]()
func fetchFriendDetail() {
let decoder = JSONDecoder()
do {
let meetingsData = try decoder.decode(TotalMeetings.self, from: response.data!)
self.friendsArr = meetingsData.friends ?? []//[meetingsData.friends].compactMap({$0}).flatMap({$0})
print(self.friendsArr)
self.tableView.reloadData()
}catch{
print(error)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return friendsArr.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! FriendsMeetingTVC
cell.emailTxt.text = friendsArr[indexPath.row].email
return cell
}
}
could anybody please let me know what I did wrong here?
I couldn't pass the JSON data from URLSession.shared.dataTask to outer extension
Nor could bring the
func numberOfSections .. etc to be used in the URLSession.shared.dataTask
Any help appreciated, thank you for your time.
Swift
struct getSubData: Decodable {
let id: Int
let name: String
}
struct Section: Decodable {
let id: Int
let name: String
let subData: [getSubData]
}
class indexViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let url = "https://nabulsi.com/nabulsi_app/main_sections_v4.json"
let urlObj = URL(string: url)
URLSession.shared.dataTask(with: urlObj!){(data, response, error) in
do {
let sections = try JSONDecoder().decode([Section].self, from: data!)
for section in sections {
print(section.name)
let sectionName = section.name
for data in section.subData {
print(data.name)
let subSectionName = data.name
}
}
} catch {
print("We got an error")
}
}.resume()
}
}
extension indexViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return sectionName.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sectionName[section].subSectionName?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = sectionName[indexPath.section].subSectionName?[indexPath.row]
return cell
}
}
JsonDecoder class has no chance to parse your data just because of the received JSON text from the server is not a valid format.
Here is your endpoint, open it in the web browser: https://nabulsi.com/nabulsi_app/main_sections_v4.json
And here is the JSON validator tool: https://jsonlint.com
Copy and paste to validate your server response to the JSON validator tool called jsonlint, and you will see where is your wrong.
Probably it's not your fault. It's about your backend developer if you didn't write by yourself. Contact him to fix the JSON format. After fixing the issue please notify me. I am gonna fix your parse codes if it's still not working as you expected.
EDIT: After fixing problem, the related codes here:
[0] - Parsed json values assigned to local variable.
class indexViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var sections: [Section] = []
override func viewDidLoad() {
super.viewDidLoad()
let url = "https://nabulsi.com/nabulsi_app/main_sections_v4.json"
let urlObj = URL(string: url)
URLSession.shared.dataTask(with: urlObj!){(data, response, error) in
do {
let sections = try JSONDecoder().decode([Section].self, from: data!)
self.sections = sections // [0]
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("We got an error")
}
}.resume()
}
}
Look at the code and follow these brief explanation:
[1] - Return every rows in section, from local array
[2] - Assigned the name variable to label for current row
[3] - Assigned the name variable to header for current section
extension indexViewController: UITableViewDataSource, UITableViewDelegate {
func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let subDatas = sections[section].subData // [1]
return subDatas.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
// [2]
let currentSection = sections[indexPath.section]
let currentSubdata = currentSection.subData[indexPath.row]
cell.textLabel?.text = currentSubdata.name
return cell
}
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let view = UIView(frame: CGRect(x: 0, y: 0, width: tableView.frame.width, height: 40))
view.backgroundColor = #colorLiteral(red: 1, green: 0.3653766513, blue: 0.1507387459, alpha: 1)
let lbl = UILabel(frame: CGRect(x: 0, y: 0, width: view.frame.width - 15, height: 40))
lbl.font = UIFont.systemFont(ofSize: 20)
lbl.text = sections[section].name // [3]
lbl.textAlignment = .right
view.addSubview(lbl)
return view
}
func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
return 40
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 40
}
}
Happy Coding! =]
Just take you section Array outside your function. Keep it as property of your controller.
class indexViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var sections: [Section] = []
override func viewDidLoad() {
super.viewDidLoad()
let url = "https://nabulsi.com/nabulsi_app/main_sections_v4.json"
let urlObj = URL(string: url)
URLSession.shared.dataTask(with: urlObj!) {
[weak self](data, response, error) in
do {
self?.sections = try JSONDecoder().decode([Section].self, from: data!)
for section in sections {
//your code goes here
}
}
catch {
print("We got an error")
}
}.resume()
}