Request with Alamofire - json

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

Related

How to pass a JSON decoded Array to another VC on swift

well Im trying to pass an array of articles which came from a news API to another VC so I will be able to display them on my TableView, now the problem is that the tableview is loaded before I updated my array, is there a way to update the array before the tableview is loaded?
MY CODE:
MY NewsViewController Code:
import UIKit
import Foundation
class NewsViewController: UITableViewController,NewsProtocol {
func didUpdateNewsArr(arr: [Articles]) {
newsArr = arr
print("I'm loaded first!!")
// print(newsArr[0].title)
}
var newsArr = [Articles]()
var newsManager = NewsManager()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.title = "Hot News"
newsManager.newsProtocolDelegate = self
newsManager.performRequest()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
}
}
// MARK: - UITableView Delegate&DataSource Methods:
extension NewsViewController {
// Return the number of rows for the table.
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return newsArr.count
}
// Provide a cell object for each row.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Fetch a cell of the appropriate type.
let cell = tableView.dequeueReusableCell(withIdentifier: "newsCell", for: indexPath)
// Configure the cell’s contents.
cell.textLabel!.text = "Cell text"
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
my networking file:
import Foundation
import UIKit
protocol NewsProtocol {
func didUpdateNewsArr(arr: [Articles])
}
struct NewsManager {
let newsURL = "https://newsapi.org/v2/everything?q=apple&from=2020-10-19&to=2020-10-19&sortBy=popularity&apiKey=009a08a56d664f1b92986e9cce27767b"
var newsProtocolDelegate: NewsProtocol?
func performRequest() {
if let url = URL(string: newsURL) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, respone, error) in
if error != nil {
print(error)
return
}
if let safeData = data {
self.parseJSON(newsData: safeData)
}
}
task.resume()
}
}
func parseJSON(newsData: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(NewsData.self, from: newsData)
let articlesArr = decodedData.articles
newsProtocolDelegate?.didUpdateNewsArr(arr: articlesArr)
} catch {
print("")
}
}
}
I've solved the problem :) thanks fellas, all was needed was tableView.reload() to reload the cells, due to the fact that the JSON request took a while and the tableView was made up before the request, I've used DispatchQue :)

JSON Parsing swift, Always catch statement executing

I have UITableViewController, i'm trying to parse data from url,
Always catch statement executing, that prints "something" in the console.
in Storyboard i added reuse identifier to the table view cell.
'''
class TableViewController: UITableViewController {
final let url = URL(string: "http://jsonplaceholder.typicode.com/posts")
private var posts = [Post]()
override func viewDidLoad() {
super.viewDidLoad()
downloadJson()
}
func downloadJson() {
guard let downloadURL = url else { return }
URLSession.shared.dataTask(with: downloadURL) { (data, response, error) in
guard let data = data, error == nil, response != nil else {
return
}
do {
let decoder = JSONDecoder()
let tempPosts = try decoder.decode(Posts.self, from: data)
print(tempPosts)
self.posts = tempPosts.posts
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("something")
}
}.resume()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = posts[indexPath.row].title
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
}
}
'''
'''
class Posts: Codable {
let posts: [Post]
init(posts: [Post]) {
self.posts = posts
}
}
class Post: Codable {
let userId: Int
let id: Int
let title: String
let body: String
init(userId: Int, id: Int, title: String, body: String) {
self.userId = userId
self.id = id
self.title = title
self.body = body
}
}
'''
If the Post model is,
struct Post: Codable {
let userId, id: Int
let title, body: String
}
Use [Posts].self instead of Posts.self while parsing the data.
let tempPosts = try decoder.decode([Post].self, from: data)

Get data from API call and show another viewController on UItableView row click

I'm new to Swift, so I ask for help. I show list of characters on tableview using API call.When user click one row, I want to call API ,get data and show it in new viewController
API call url looks like :
https://rickandmortyapi.com/api/character/{user_clicked_row_charactor_id}
Sample response for id = 3
{
"id": 3,
"name": "Summer Smith",
"status": "Alive",
"species": "Human",
"type": "",
"gender": "Female",
}
And so it is necessary for each of the characters. Tell or direct how to implement this?
Sorry for my bad English.
My code:
class UsersTableViewController: UITableViewController {
var characters = [Results]()
override func viewDidLoad() {
super.viewDidLoad()
LoadCharacters()
}
func LoadCharacters() {
let urlString = "https://rickandmortyapi.com/api/character/"
if let url = URL(string: urlString)
{
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, responce, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
self.parseJson(usersData: safeData)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
task.resume()
self.tableView.reloadData()
}
}
func parseJson(usersData: Data) {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(JSONData.self, from: usersData)
characters = decodedData.results
print(decodedData.results[0].name)
} catch {
print(error)
}
}
struct JSONData: Decodable {
let results: [Results]
}
struct Results: Decodable {
let name: String
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return characters.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "user", for: indexPath)
let guys = characters[indexPath.row]
cell.textLabel?.text = guys.name
return cell
}
}
If I understand your question properly, you’d need to implement the didSelectRowAt of the table view method then pass the necessary data to the new view controller:
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let character = characters[indexPath.row]
let charactersViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(identifier: "characterVC") as! CharacterDetailViewController
charactersViewController.character = character
present(charactersViewController.character, animated: true)
}
For this to work, you will need to have another view controller to show the character once tapped on. Give that view controller the Storyboard ID of "characterVC" in the Identity inspector. The view controller will also need a variable to receive the character object, perhaps:
class CharacterDetailViewController: UIViewController {
var character: Character?
override func viewDidLoad() {
super.viewDidLoad()
if let character = character {
// show the character on the view
}
}
}

TableView not populating data, but JSON file is being processed

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

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.