TableView not populating data, but JSON file is being processed - json

I am having a problem getting my tableview to populate from my hosted JSON file. I've confirmed the app is successfully seeing the data within the JSON file, but the table itself is still blank (and oddly, shows two different vertical heights for some of the rows).
Here is my ViewController.swift:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
// var heroes = [HeroStats]()
var bonuses = [JsonFile.JsonBonuses]()
override func viewDidLoad() {
super.viewDidLoad()
downloadJSON {
self.tableView.reloadData()
}
tableView.delegate = self
tableView.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Found \(bonuses.count) rows in section.")
return bonuses.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel?.text = bonuses[indexPath.row].name.capitalized
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "showDetails", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let destination = segue.destination as? HeroViewController {
destination.bonus = bonuses[(tableView.indexPathForSelectedRow?.row)!]
}
}
// MARK: - Download JSON from ToH webserver
func downloadJSON(completed: #escaping () -> ()) {
let url = URL(string: "http://tourofhonor.com/BonusData.json")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil {
do {
let posts = try JSONDecoder().decode(JsonFile.self, from: data!)
DispatchQueue.main.async {
completed()
}
print(posts.bonuses.map {$0.bonusCode})
} catch {
print("JSON Download Failed")
}
}
}.resume()
}
}
And here is what the JsonFile.swift file looks like:
import Foundation
struct JsonFile: Codable {
struct Meta: Codable {
let fileName: String
let version: String
}
struct JsonBonuses: Codable {
let bonusCode: String
let category: String
let name: String
let value: Int
let city: String
let state: String
let flavor: String
let imageName: String
}
let meta: Meta
let bonuses: [JsonBonuses]
}
That print within the tableView numberOfSections displays 0, and I've noticed I see that printed three times, then I see the print of the codes indicating the JSON was read, then I see the "Found 0 rows in section" print again.
What am I missing here?

In the datasource method you are reading from bonuses array. But when you are done downloading the posts you aren't assigning the bonuses of the post to your bonuses array.
func downloadJSON(completed: #escaping () -> ()) {
let url = URL(string: "http://tourofhonor.com/BonusData.json")
URLSession.shared.dataTask(with: url!) { [weak self] (data, response, error) in
if error == nil {
do {
let posts = try JSONDecoder().decode(JsonFile.self, from: data!)
DispatchQueue.main.async {
completed()
}
print(posts.bonuses.map {$0.bonusCode})
// Here you need to assign the bonuses from your posts to your bonuses array
// Pay attention to the [weak self] that is added in the function call
self?.bonuses = ... // do anything that converts to bonuses
} catch {
print("JSON Download Failed")
}
}
}.resume()
}

Related

How to get data from an API using Swift

I'm trying to get data from an API into my table view, but app goes into the catch error " json error". I'll share the code with you.
class ViewController: UIViewController {
#IBOutlet weak var homeTableView: UITableView!
var repository = [RepositoryStats]()
override func viewDidLoad() {
super.viewDidLoad()
downloadJSON {
self.homeTableView.reloadData()
}
homeTableView.delegate = self
homeTableView.dataSource = self
}
func downloadJSON (completed: #escaping () -> ()) {
let url = URL (string: "https://api.github.com/search/repositories?q=language:Swift+language:RXSwift&sort=stars&order=desc")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil {
do {
self.repository = try JSONDecoder().decode([RepositoryStats].self, from: data!)
DispatchQueue.main.async {
completed()
}
}catch {
print ("json error")
}
}
}.resume()
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return repository.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
cell.textLabel?.text = repository[indexPath.row].full_name
return cell
}
This is my struct where I declared :
struct RepositoryStats: Decodable {
let items: [Item]
}
struct Item: Decodable {
let fullName: String
}
If anyone know, why I am getting into the "json error" catch ? Thank you !
Link : https://api.github.com/search/repositories?q=language:Swift+language:RXSwift&sort=stars&order=desc
I fixed it. I have changed my struct into this :
struct RepositoryStats: Decodable {
let items: [Item]
}
struct Item: Decodable {
let full_name: String
let forks_count: Int
let stargazers_count: Int
}
And this is my downloaad JSON func.
func downloadJSON (completed: #escaping () -> ()) {
let url = URL (string: "https://api.github.com/search/repositories?q=language:Swift+language:RXSwift&sort=stars&order=desc")
URLSession.shared.dataTask(with: url!) { (data, response, error) in
if error == nil {
do {
self.repository = try JSONDecoder().decode(RepositoryStats.self, from: data!)
DispatchQueue.main.async {
completed()
}
}catch {
print (error)
}
}
}.resume()
}
}

UITableView returning nil when trying to call reloadData()

I am trying to get my table view to show json data from the news api. I've been able to parse the data and display it to the console but a nil value was caught in the self.tableview.reload(). I need help in resolving the issue
let urlRequest = "https://newsapi.org/v2/everythingq=Coronavirus&sortBy=publishedAt&apiKey"
var articles: [Articles]? = []
#IBOutlet weak var tableview: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
retriveData()
}
func retriveData(){
guard let aritcleUrl = URL(string: urlRequest) else {
return
}
let request = URLRequest(url: aritcleUrl)
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) -> Void in
if error != nil {
print(error ?? 0)
return
}
if let data = data {
self.articles = self.parseData(data: data)
// Reload table view
DispatchQueue.main.async {
self.tableview.reloadData()
}
}
})
task.resume()
}
func parseData(data:Data)-> [Articles] {
var articles: [Articles]? = []
do {
let jsonResult = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? NSDictionary
let jsonArticles = jsonResult?["articles"] as? [AnyObject] ?? []
for jsonArticle in jsonArticles{
let article = Articles()
article.author = jsonArticle["author"] as? String
article.title = jsonArticle["title"] as? String
article.publishedAt = jsonArticle["publishedAt"] as? String
articles?.append(article)
}
print(jsonArticles)
} catch {
print(error)
}
return articles ?? []
}
It seems that your class doesn't conform to table view's protocols.
You should edit your declaration to:
class <YourViewController>: UIViewController, UITableViewDelegate, UITableViewDataSource {
then in the viewDidLoad you should set the delegate and datasource to your tableview
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
retriveData()
}
Then you have to add the protocol stubs as suggested by xcode
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
self.articles?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// YOUR CELL CUSTOMIZATION GOES HERE
}
However I suggest you to look at any TableView tutorial on Internet, such as the Apple's official one
here

Request with Alamofire

I'm starting on swift and I'm trying to bring a list of cars with alamofire but it is not bringing
The code executes without throwing errors but not the list
I see a blank table view
https://uploaddeimagens.com.br/imagens/simulator_screen_shot_-_iphone_8_-_2019-10-20_at_16-48-23-png
(sorry... editing with the write code)
My classes
struct HotelData: Codable {
let results: [Results]
}
struct Results: Codable {
let smallDescription: String
let price: Price
let gallery: [ImageHotel]
let name: String
let address: Address
var getRandonImage: ImageHotel {
let index = Int(arc4random_uniform(UInt32(gallery.count)))
return gallery[index]
}
}
==============
My manager
class func getHotels(onComplete: #escaping (HotelData?) -> Void) {
AF.request(path).responseJSON { (response) in
guard let jsonData = response.data else { return }
guard let hotelData = try? JSONDecoder().decode(HotelData.self, from: jsonData)
else {
onComplete(nil)
return
}
onComplete(hotelData)
return
}
}
}
==============
Cell
func prepareCell(with hotel: Results){
lbName.text = hotel.name
lbSmallDescription.text = hotel.smallDescription
lbPrice.text = "R$ \(hotel.price.amount)"
lbAdress.text = hotel.address.city
}
==============
TableView
class HotelTableViewController: UITableViewController {
var hotels: [Results] = []
let hotelManager = HotelManager()
override func viewDidLoad() {
super.viewDidLoad()
loadHotels()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return hotels.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! HotelTableViewCell
let hotel = hotels[indexPath.row]
cell.prepareCell(with: hotel)
return cell
}
func loadHotels() {
HotelManager.getHotels { (hotelData) in
if let hotelData = hotelData {
self.hotels += hotelData.results
}
}
}
}
Your direct issue is that you need to call reloadData() if you want your UITableView to look at your downloaded data and load the appropriate cells. You can put in the completion handler of your network call, or in a didSet on your hotels property.
Additionally, you shouldn't use responseJSON for Decodable types, it's redundant. Instead you should use responseDecodable and pass the type you want to parse: responseDecodable(of: HotelData.self) { response in ... }.

Table empty when populating cells from json

I am trying to populate a table with json content. Everything seems to work fine except that the table is not showing any data. Actually, the code shown below should display the "title" information of each json data array into one cell. See line
cell.textLabel?.text = myNewsItems[indexPath.row].title
However, from what I can see in the console output, I can verify that the news array is parsed like expected (see Checkpoint: print(myNewsS)).
Any idea what I am missing?
Swift4
import UIKit
// structure from json file
struct News: Codable{
let type: String
let timestamp: String
let title: String
let message: String
}
class HomeVC: UIViewController, UITableViewDelegate, UITableViewDataSource{
var myTableView = UITableView()
var myNewsItems: [News] = []
override func viewDidLoad() {
super.viewDidLoad()
let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height
let displayWidth: CGFloat = self.view.frame.width
let displayHeight: CGFloat = self.view.frame.height
myTableView = UITableView(frame: CGRect(x: 0, y: 150, width: displayWidth, height: displayHeight - barHeight))
myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
myTableView.dataSource = self
myTableView.delegate = self
self.view.addSubview(myTableView)
// JSON
let url=URL(string:"https://api.myjson.com/bins/ywv0k")
let session = URLSession.shared
let task = session.dataTask(with: url!) { (data, response, error) in
// check status 200 OK etc.
guard let data = data else { return }
do {
let myNewsS = try
JSONDecoder().decode([News].self, from: data)
print(myNewsS)
DispatchQueue.main.async {
self.myTableView.reloadData()
}
} catch let jsonErr {
print("Error json:", jsonErr)
}
}
task.resume()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myNewsItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = myNewsItems[indexPath.row].title
return cell
}
}
Assign the array
myNewsItems = myNewsS
DispatchQueue.main.async {
self.myTableView.reloadData()
}

data is not filtered by the searchbar

I am trying to filter my fetched JSON data using a searchbar. However, when I type something into the searchbar it does nothing. Data is still in the same place and it is not filtered, whereas it should be dynamically filtered while I am typing something into the searchbar.
The code below shows my TableViewController as well as the function for fetching JSON data into my array. It is then filtered using a searchbar and whenever the data's name is matching the condition in the search bar it is then added to the second array called 'filteredExercise'.
import UIKit
class ExerciseTableViewController: UITableViewController, UISearchBarDelegate {
var fetchedExercise = [Exercise]()
var filteredExercise = [Exercise]()
var inSearchMode = false
#IBOutlet var searchBar: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
searchBar.delegate = self
parseData()
}
func parseData() {
fetchedExercise.removeAll()
let urlPath = "https://wger.de/api/v2/exercise/?format=json&language=2&status=2&limit=200"
let url = URL(string: urlPath)!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Error while parsing JSON")
}
else {
do {
if let data = data,
let fetchedData = try JSONSerialization.jsonObject(with: data, options: .mutableLeaves) as? [String:Any],
let exercises = fetchedData["results"] as? [[String: Any]] {
for eachExercise in exercises {
if eachExercise["license_author"] as! String == "wger.de" {
let name = eachExercise["name"] as! String
let description = eachExercise["description"] as! String
let id = eachExercise["id"] as! Int
self.fetchedExercise.append(Exercise(name: name, description: description, id: id))
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
catch {
print("Error while parsing data.")
}
}
}
task.resume()
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if inSearchMode {
return filteredExercise.count
}
return fetchedExercise.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "ExerciseCell", for: indexPath) as? ExerciseCell {
let exercise: Exercise!
if inSearchMode {
exercise = filteredExercise[indexPath.row]
cell.configureCell(exercise: exercise)
} else {
exercise = fetchedExercise[indexPath.row]
cell.configureCell(exercise: exercise)
}
return cell
} else {
return UITableViewCell()
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
var exercise: Exercise!
exercise = fetchedExercise[indexPath.row]
performSegue(withIdentifier: "exerciseDetailVC", sender: exercise)
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
inSearchMode = false
self.tableView.reloadData()
} else {
inSearchMode = true
let lower = searchBar.text!.lowercased()
filteredExercise = fetchedExercise.filter({$0.name.range(of: lower) != nil})
self.tableView.reloadData()
}
}
}
Looks like you have an error in?
#IBOutlet var searchBar: UITableView!
I think it should be type of UISearchBarController.
Okay, I have finally figured out what is wrong with it.
Basically first of all I had incorrect type for my searchBar due to the Xcode bug and I did not see that.
Then I had to connect my IBOutlet to the storyboard as well because it was not done.
Finally I started getting wrong results while filtering through the data and it was because I have been filtering through results using a lowercased() function, whereas all my data is capitalized.