Couldn't use JSON Data in an viewController extension? - arrays

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()
}

Related

How to put JSON response to a tableview

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") }
}

parsing nested arrays to populate uitableview in swift

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
}
}

populate tableview cell from complex api json using swift

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

Populating an UITableview from a struct

I have two viewControllers one called programlist that displays the list of tiles and populates a a suitable view.
the second viewController inputs the data. Issues implementing the callback due to an error in the prepareForsegue function. Getting the error "Instance member 'callback' cannot be used on type 'addWorkout'"
viewController 1 aka Programlist:
import UIKit
struct Item: Codable {
var title: String
var others: [String]
}
class ProgramList: UIViewController, UITableViewDataSource, UITableViewDelegate{
var Programs = [Item]()
#IBOutlet weak var programTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
load()
}
//saving current state of programs array
func save() {
guard let data = try? JSONEncoder().encode(Programs) else { return }
UserDefaults.standard.set(data, forKey: "notes")
}
//loading saved program array
func load() {
guard let loadedData = UserDefaults.standard.data(forKey: "notes") else { return }
do {
Programs = try JSONDecoder().decode([Item].self, from: loadedData)
programTableView.reloadData()
} catch { print(error) }
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Programs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.programTitle.text = Programs[indexPath.row].title
return cell
}
//Removing Item by swipping left & saving this newly established array
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == UITableViewCell.EditingStyle.delete {
Programs.remove(at: indexPath.row)
programTableView.reloadData()
save()
}
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toAddPage"{
workoutController.callback = { [weak self] string in
let entered = Item(title: string, others: ["hi"])
self?.programs.append(entered)
let indexPath = IndexPath(row: self?.programs.count - 1, section: 0)
self?.tableView.insertRows(at: [indexPath], with: .automatic)
self?.save()
}
}
}
}
}
}
viewController 2 aka addWorkout:
import UIKit
class addWorkout: UIViewController {
#IBOutlet weak var workoutTitle: UITextField!
var callback : ((String) -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func saveWorkoutTitle(_ sender: Any) {
if !workoutTitle.text!.isEmpty {
callback?(workoutTitle.text!)
}
}
}
The main mistake is you are trying to save an array of Item – which is not supported anyway – to UserDefaults and read an array of String. That's a clear type mismatch.
To be able to save an array of a custom struct to UserDefaults adopt Codable to save the struct as JSON.
struct Item : Codable {
var title: String
var others: [String]
}
Further it's a very bad practice to declare a data source array outside of any class.
This is the ProgramList class with adjusted load and save methods and the data source array inside the class. The method viewDidAppear is not needed.
class ProgramList: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var programTableView: UITableView!
var programs = [Item]()
override func viewDidLoad() {
super.viewDidLoad()
load()
}
//saving current state of programs array
func save() {
guard let data = try? JSONEncoder().encode(programs) else { return }
UserDefaults.standard.set(data, forKey: "notes")
}
//loading saved program array
func load() {
guard let loadedData = UserDefaults.standard.data(forKey: "notes") else { return }
do {
programs = try JSONDecoder().decode([Item].self, from: loadedData)
programTableView.reloadData()
} catch { print(error) }
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return programs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
cell.programTitle.text = programs[indexPath.row].title
return cell
}
//Removing Item by swipping left & saving this newly established array
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
programs.remove(at: indexPath.row)
programTableView.deleteRows(at: [indexPath], with: .automatic)
save()
}
}
}
To share data between controllers use a closure as callback and pass the string
class AddWorkout: UIViewController {
#IBOutlet weak var workoutTitle: UITextField!
var callback : ((String) -> Void)?
#IBAction func saveWorkoutTitle(_ sender: Any) {
if !workoutTitle.text!.isEmpty {
callback?(workoutTitle.text!)
}
}
}
Back in ProgramList controller assign a closure to the callback property in prepareForSegue (or right before presenting the controller)
func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toAddPage" {
let workoutController = segue.destination as! AddWorkout
workoutController.callback = { string in
let entered = Item(title: string, others: ["hi"])
self.programs.append(entered)
let indexPath = IndexPath(row: self.programs.count - 1, section: 0)
self.tableView.insertRows(at: [indexPath], with: .automatic)
self.save()
}
}
}

Table View To seperate view with segue

This has stumped me and Ive tried a few ways of doing of tutorials and on stack answers but its still not building.
So basically:
Im getting core data and then placing that into an array. (works fine)
After that Im just display first and last name in the cells (works fine)
User taps on cell to see athleteDetalView
I have placed a few print statements to see where its going wrong.
Thanks In advance for your input.
import UIKit
import CoreData
class athleteViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
//labels
#IBOutlet weak var athleteLabel: UILabel!
//buttons
#IBOutlet weak var athleteCreate: UIBarButtonItem!
#IBOutlet weak var athleteTableView: UITableView!
var athleteArray:[Athlete] = []
var myIndex = 0
override func viewDidLoad() {
super.viewDidLoad()
athleteTableView.delegate = self
athleteTableView.dataSource = self
self.fetchData()
self.athleteTableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return athleteArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "athleteName", for: indexPath)
let name = athleteArray[indexPath.row]
cell.textLabel!.text = name.firstName! + " " + name.lastName!
let athleteName = name.firstName!
let lastName = name.lastName!
let age = name.age!
let sport = name.sport!
let email = name.email!
print(athleteName)
print(lastName)
print(age)
print(sport)
print(email)
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
myIndex = indexPath.row
let currentCell
//let storyboard = UIStoryboard(name: "Main", bundle: nil)
//let destination = storyboard.instantiateViewController(withIdentifier: "athleteDetailsViewController") as! athleteDetailsViewController
//let athName = athleteArray[myIndex]
//testdata
//test data
performSegue(withIdentifier: "athleteDetailsSegue", sender: self)
//self.navigationController?.pushViewController(destination, animated: true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if (segue.identifier == "athleteDetailsSegue") {
var destination = segue.destination as! athleteDetailsViewController
destination.firstNameString = athleteName
destination.lastNameString = lastName
destination.ageString = age
destination.sportString = sport
destination.emailString = email
//destination.getImage = name.image
}
}
func fetchData(){
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do{
athleteArray = try context.fetch(Athlete.fetchRequest())
}
catch{
print(error)
}
}
The variables that you are setting in tableView:cellForRowAt:indexPath: are local variables and are not available in the function prepareForSegue:. Try to declaring the variables at the top as properties of the class.

Resources