Swift: How to Load JSON in different Table View - ios

Hi everybody I'm new to Swift and I need help.
So I have three different JSON that will show in different moments, The first JSON has been loading perfectly, but when I clicking on the item to reload another JSON and show the detail nothing appears.
I'm confused about details:
Need I have three differents table Views for each JSON? or the only one is enough?
When I working with data (JSON) need I use a specific function to prepare the new JSON that will appear as "prepare"?
In my project I have:
Two view controllers: ViewController(default) and DetailViewController.
In my Main.Storyboard: Tab Bar Controller --> Navigation --> Table View
The code of the first View controller:
import UIKit
class ViewController: UITableViewController {
var categories = [Category]()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let urlString = "https://www.themealdb.com/api/json/v1/1/categories.php"
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url) {
parse(json: data)
} else {
print("error connecting")
}
}
}
func parse(json: Data) {
let decoder = JSONDecoder()
print("parse called")
do {
let jsonCategories = try decoder.decode(Categories.self, from: json)
categories = jsonCategories.categories
tableView.reloadData()
} catch {
print("error parsin: \(error)")
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categories.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let category = categories[indexPath.row]
cell.textLabel?.text = category.idCategory
cell.detailTextLabel?.text = category.strCategoryDescription
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let newViewController = DetailViewController()
self.navigationController?.pushViewController(newViewController, animated: true)
}
}
The code of the second view controller:
import UIKit
import WebKit
class DetailViewController: UIViewController {
var webView: WKWebView!
var meals = [Meal]()
override func loadView() {
webView = WKWebView()
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "https://www.themealdb.com/api/json/v1/1/filter.php?c=Beef"
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url) {
parse(json: data)
} else {
print("error connecting")
}
}
}
func parse(json: Data) {
let decoder = JSONDecoder()
print("parse called")
do {
let jsonMeals = try decoder.decode(Meals.self, from: json)
meals = jsonMeals.meals
print(String(format:"read %d meals", meals.count))
} catch {
print("error parsin: \(error)")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return meals.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let meal = meals[indexPath.row]
cell.textLabel?.text = meal.idMeal
cell.detailTextLabel?.text = meal.strMeal
return cell
}
}

The function parse(json: Data) on DetailViewController does not call tableView.reloadData() so the new data cannot be loaded to UITableView
Need I have three differents table Views for each JSON? or the only one is enough?
If you're loading different JSON files but want to show in the same view. You only need 1 UITableViewCell.
When you load & decode JSON, consider moving it to background queue to avoid blocking main thread.

Related

How do you display JSON API data in a table view?

I am not able to display the JSON data in my table view. I don't know why. I tried to get the JSON data, but I am not able to display it on screen in a table format.
This is the model:
class PastTripsVC: UIViewController {
var past = [PastRide]()
#IBOutlet weak var mTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let nibCell = UINib(nibName: "PastTableView", bundle: nil)
mTable.register(nibCell, forCellReuseIdentifier: "cell")
apiCalling()
}
func apiCalling(){
if let url = URL(string: "https://pincood.com/pincood/public/api/user/trips") {
var request = URLRequest(url: url)
request.allHTTPHeaderFields = [
"Content-Type": "application/json",
"Session": "fb4e7f9b-0f31-4709-",
"AUthorization":"<some key>"
]
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard error == nil else { return }
guard let data = data else { return }
do{
let codabledata = try JSONDecoder().decode([PastRide].self, from: data)
print(codabledata)
DispatchQueue.main.async {
self.past = codabledata
self.mTable.reloadData()
}
} catch {
print(error)
}
}.resume()
}
}
In the extension we try:
extension PastTripsVC : UITableViewDelegate, UITableViewDataSource{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return past.count
print(past.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! PastTableView
cell.usernm.text = past[indexPath.row].provider.firstName
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let details : PastDetailView = self.storyboard?.instantiateViewController(withIdentifier: "PastDetailView") as! PastDetailView
navigationController?.pushViewController(details, animated: true)
}
}
You need to set table view's delegate and datasource properties in viewDidLoad.
Update your viewDidLoad to look like this:
override func viewDidLoad() {
super.viewDidLoad()
let nibCell = UINib(nibName: "PastTableView", bundle: nil)
mTable.register(nibCell, forCellReuseIdentifier: "cell")
mTable.delegate = self
mTable.datasource = self
apiCalling()
}

passing data from tableview to viewContoller in swift

I have a tableview loads data from php json using Alamofire, it loads perfect on the tableview, now I tried to pass the data to a second viewController to show more details, I faced this error which says Cannot find the data in scope
func getUsers() {
AF.request(SiteUrl).validate().responseJSON { response in
switch response.result {
case .success:
print("Validation Successful)")
if let json = response.data {
do{
let jsonData = try JSON(data: json)
self.data = jsonData.arrayValue
self.tableView.reloadData() // we are already on the main thread
//print("DATA PARSED: \(jsonData)")
}
catch {
print("JSON Error", error)
}
}
case .failure(let error):
print(error)
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let customCell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.identifier, for: indexPath) as! TableViewCell
let item = data[indexPath.row]
customCell.AdTitle?.text = item["title"].string
customCell.AdDate?.text = item["time"].string
let imageUrl = item["document"].string
let url = NSURL(string:("https://qateef-ads.co/uploads/" + imageUrl!))
customCell.AdImage.sd_setImage(with: url as URL?, placeholderImage: UIImage(named: "placeholder.png"))
return customCell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = storyboard?.instantiateViewController(withIdentifier: "DetailsViewController") as? DetailsViewController
self.navigationController?.pushViewController(vc!, animated: true)
vc?.imageView = UIImage(named: url)
vc?.AdTitle = item["title"].string
}
how can I pass the data from to the second view?
I'm not sure I understood what exactly is your problem. But if you need to access that data on the second view controller, the simplest way would be to inject that data into the DetailsViewController you're instantiating when the cell is selected.
But first you need to create that property:
class DetailsViewController: UIViewController {
var data: JSON?
...
}
Then when instantiating that view controller:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
guard let vc = storyboard?.instantiateViewController(withIdentifier: "DetailsViewController") as? DetailsViewController else { return }
vc.data = data[indexPath.row]
self.navigationController?.pushViewController(vc, animated: true)
}
Now you can use that data on your DetailsViewController to pull any information you need from the json response:
class DetailsViewController: UIViewController {
...
override viewDidLoad() {
super.viewDidLoad()
let title = data["title"]
...
}
}
You pass your alamofire result to "data" variable. Therefore, you should use "data" while passing the value to another VC. In your case, it could be like;
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let vc = storyboard?.instantiateViewController(withIdentifier: "DetailsViewController") as? DetailsViewController
vc?.imageView = UIImage(named: url)
let item = data[indexPath.row]
vc?.AdTitle = item["title"].string
self.navigationController?.pushViewController(vc!, animated: true)
}
Ok
**
if let json = response.data {
let JSON22 = json as! NSDictionary
do{
let jsonData = try JSON(data: JSON22)
self.data = jsonData.arrayValue
self.tableView.reloadData() // we are already on the main thread
//print("DATA PARSED: \(jsonData)")
}
catch {
print("JSON Error", error)
}
**
If you want to pass your data from Controller1 to Controller2.
In Controller1
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let item = data[indexPath.row]
let vc = storyboard?.instantiateViewController(withIdentifier: "DetailsViewController") as? DetailsViewController
vc!.dictGetData = item
self.navigationController?.pushViewController(vc!, animated: true)
}
In Controller2
Create a Dictionary variable
var dictGetData:NSDictionary!
then print dictionary on view did load
override func viewDidLoad() {
super.viewDidLoad()
print(dictGetData as Any)
}

Error setting UILabel text in custom UITableViewCell

I am quite new to Swift programming, but I am having trouble setting UILabel text in my UITableView class for individual UITableViewCell instances.
I have created a custom subclass of UITableViewCell called PizzaTableViewCell and a custom UITableView class called PizzaListTableViewController. I am trying to populate the UITableView instance with data from an array, which is being populated from an API call to my node.js server.
I have included my UITableView subclass, custom UITablveViewCell class, the struct for the data, and a link to a screenshot of the Simulator loading what I have done. Any help is greatly appreciated!
I have verified that the data is being put in the array with no issues, as I can print the contents after the call to fetchInventory method. I have been able to set a single textLabel with
cell.textLabel?.text = pizzas[indexPath.row].name
along with an image in the array with:
cell.imageView?.image = pizzas[indexPath.row].image
but I have 2 more labels that I need in each cell which I cannot set. I have checked my IBOutlets and Storyboard identifiers, and they match the code.
class PizzaListTableViewController: UITableViewController {
var pizzas: [Pizza] = []
override func viewDidLoad() {
super.viewDidLoad()
//title you will see on the app screen at the top of the table view
navigationItem.title = "Drink Selection"
tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza")
//tableView.estimatedRowHeight = 134
//tableView.rowHeight = UITableViewAutomaticDimension
fetchInventory { pizzas in
guard pizzas != nil else { return }
self.pizzas = pizzas!
print(self.pizzas)
//self.tableView.reloadData()
//print(self.pizzas)
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
} //end of viewDidLoad
private func fetchInventory(completion: #escaping ([Pizza]?) -> Void) {
Alamofire.request("http://127.0.0.1:4000/inventory", method: .get)
.validate()
.responseJSON { response in
guard response.result.isSuccess else { return completion(nil) }
guard let rawInventory = response.result.value as? [[String: Any]?] else { return completion(nil) }
let inventory = rawInventory.compactMap { pizzaDict -> Pizza? in
var data = pizzaDict!
data["image"] = UIImage(named: pizzaDict!["image"] as! String)
//print(data)
//print("CHECK")
print("Printing each item: ", Pizza(data: data))
//printing all inventory successful
return Pizza(data: data)
}
completion(inventory)
}
}
#IBAction func ordersButtonPressed(_ sender: Any) {
performSegue(withIdentifier: "orders", sender: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//PRINTING ROWS 0 TWICE in console
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("ROWS", pizzas.count)
return self.pizzas.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: PizzaTableViewCell = tableView.dequeueReusableCell(withIdentifier: "Pizza", for: indexPath) as! PizzaTableViewCell
//cell.backgroundColor = Services.baseColor
//cell.pizzaImageView?.image = pizzas[indexPath.row].image
//THESE WORK BUT ARE A STATIC WAY OF SETTING THE CELLS
//CAN ONLY SET THE SELL WITH A SINGLE TEXT LABEL FROM THE DATA ARRAY
cell.imageView?.image = pizzas[indexPath.row].image
cell.textLabel?.text = pizzas[indexPath.row].name
//cell.textLabel?.text = pizzas[indexPath.row].description
//cell.textLabel?.text = "$\(pizzas[indexPath.row].amount)"
// cell.name?.text = pizzas[indexPath.row].name
// cell.imageView?.image = pizzas[indexPath.row].image
// cell.amount?.text = "$\(pizzas[indexPath.row].amount)"
// cell.miscellaneousText?.text = pizzas[indexPath.row].description
//print(cell.name?.text! as Any)
print(cell.imageView as Any)
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0
} //END OF
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "pizzaSegue", sender: self.pizzas[indexPath.row] as Pizza)
} //END OF override func tableView
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "pizzaSegue" {
guard let vc = segue.destination as? PizzaViewController else { return }
vc.pizza = sender as? Pizza
}
} //END OF override preppare func
}
class PizzaListTableViewController: UITableViewController {
var pizzas: [Pizza] = []
override func viewDidLoad() {
super.viewDidLoad()
//title you will see on the app screen at the top of the table view
navigationItem.title = "Drink Selection"
tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza")
//tableView.estimatedRowHeight = 134
//tableView.rowHeight = UITableViewAutomaticDimension
fetchInventory { pizzas in
guard pizzas != nil else { return }
self.pizzas = pizzas!
print(self.pizzas)
//self.tableView.reloadData()
//print(self.pizzas)
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
} //end of viewDidLoad
private func fetchInventory(completion: #escaping ([Pizza]?) -> Void) {
Alamofire.request("http://127.0.0.1:4000/inventory", method: .get)
.validate()
.responseJSON { response in
guard response.result.isSuccess else { return completion(nil) }
guard let rawInventory = response.result.value as? [[String: Any]?] else { return completion(nil) }
let inventory = rawInventory.compactMap { pizzaDict -> Pizza? in
var data = pizzaDict!
data["image"] = UIImage(named: pizzaDict!["image"] as! String)
//print(data)
//print("CHECK")
print("Printing each item: ", Pizza(data: data))
//printing all inventory successful
return Pizza(data: data)
}
completion(inventory)
}
}
#IBAction func ordersButtonPressed(_ sender: Any) {
performSegue(withIdentifier: "orders", sender: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
//PRINTING ROWS 0 TWICE in console
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("ROWS", pizzas.count)
return self.pizzas.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: PizzaTableViewCell = tableView.dequeueReusableCell(withIdentifier: "Pizza", for: indexPath) as! PizzaTableViewCell
//cell.backgroundColor = Services.baseColor
//cell.pizzaImageView?.image = pizzas[indexPath.row].image
//THESE WORK BUT ARE A STATIC WAY OF SETTING THE CELLS
//CAN ONLY SET THE SELL WITH A SINGLE TEXT LABEL FROM THE DATA ARRAY
cell.imageView?.image = pizzas[indexPath.row].image
cell.textLabel?.text = pizzas[indexPath.row].name
//cell.textLabel?.text = pizzas[indexPath.row].description
//cell.textLabel?.text = "$\(pizzas[indexPath.row].amount)"
// cell.name?.text = pizzas[indexPath.row].name
// cell.imageView?.image = pizzas[indexPath.row].image
// cell.amount?.text = "$\(pizzas[indexPath.row].amount)"
// cell.miscellaneousText?.text = pizzas[indexPath.row].description
//print(cell.name?.text! as Any)
print(cell.imageView as Any)
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0
} //END OF
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "pizzaSegue", sender: self.pizzas[indexPath.row] as Pizza)
} //END OF override func tableView
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "pizzaSegue" {
guard let vc = segue.destination as? PizzaViewController else { return }
vc.pizza = sender as? Pizza
}
} //END OF override preppare func
}
struct Pizza {
let id: String
let name: String
let description: String
let amount: Float
//let amount: String
let image: UIImage
init(data: [String: Any]) {
//print("CHECK:: pizza.swift")
self.id = data["id"] as! String
self.name = data["name"] as! String
// self.amount = data["amount"] as! Float
self.amount = ((data["amount"] as? NSNumber)?.floatValue)!
self.description = data["description"] as! String
self.image = data["image"] as! UIImage
}
}
As noted above, I have been able to print the contents of the data array with beer names, pictures, descriptions and etc. I have tried to print to console
print(cell.name?.text)
after setting
cell.name?.text = pizzas[indexPath.row].name
but it prints nil and this is a problem. I have been stuck with this for about 2 weeks!
IBOutlets screenshot:
I think i found your Problem, let me explain
What you are doing here is you have a custom UITableViewCell defined in the Storyboard in a Controller named "Root View Controller" which is not your PizzaListTableViewController to put it simply
And as you said you have absolutely no issue regarding the IBOutlets
Now when you say
tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza")
In Your PizzaListTableViewController you are not linking it with the UI of the cell rather just the Code (This is only used when there is no xib of the cell)
Now what you can do to solve this
Solution # 1
Move/Copy your UI of the PizzaTableViewCell to PizzaListTableViewController in the storyboard from your "Root View Controller"
Make sure you add a Reuse Identifier in the Attribute Inspector of the cell in the storyboard
remove tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza") this wont give you an error this time as it will automatically get register
Make sure all the IBOutlets are connected
Solution # 2
create a separate Nib (xib) of the cell
and now you have to register the cell here like
tableView.register(UINib(nibName: "PizzaTableViewCell", bundle: Bundle.main), forCellReuseIdentifier: "PizzaCell")
Hope this helps.
Try this
cell.name?.text = ...
cell.amount?.text = ...
cell.miscellaneousText?.text = ...
cell.pizzaImageView?.image = ...
If it still does not work then make sure your cell and your outlets are not null when setting its value. Hope it helps !
There is something definitely strange going on with your setup.
If you try to name the IBOutlets with the same name as the UITableViewCell default property it'll throw an error. The fact that you were able to set those names and build successfully is strange.
From the screenshot above you can see what happens when I attempted to do this.
Make sure your Table View Controller class is set in the storyboard.
Make sure your Table View Cell class is set in the storyboard.
Make sure that all your outlets are properly connected.
Make sure your Table View Cell Identifier is provided in the storyboard.
My Table View Controller Subclass
My Table View Cell Subclass
cell.imageView?.image and cell.textLabel?.text are optional properties of the table view itself. They are not the properties of the custom cell that you designed.
You use tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "Pizza") when you have designed a table view cell in XIB. But as you have designed the cell in the storyboard itself you should set the cell reuse identifier and cell class in the storyboard.
I hope this will help you out.

Limit the amount of cells shown in tableView, load more cells when scroll to last cell

I'm trying to set up a table view that only shows a specific amount of cells. Once that cell has been shown, the user can keep scrolling to show more cells. As of right now I'm retrieving all the JSON data to be shown in viewDidLoad and storing them in an array. Just for example purposes I'm trying to only show 2 cells at first, one the user scrolls to bottom of screen the next cell will appear. This is my code so far:
class DrinkViewController: UIViewController {
#IBOutlet weak var drinkTableView: UITableView!
private let networkManager = NetworkManager.sharedManager
fileprivate var totalDrinksArray: [CocktailModel] = []
fileprivate var drinkImage: UIImage?
fileprivate let DRINK_CELL_REUSE_IDENTIFIER = "drinkCell"
fileprivate let DRINK_SEGUE = "detailDrinkSegue"
var drinksPerPage = 2
var loadingData = false
override func viewDidLoad() {
super.viewDidLoad()
drinkTableView.delegate = self
drinkTableView.dataSource = self
networkManager.getJSONData(function: urlFunction.search, catagory: urlCatagory.cocktail, listCatagory: nil, drinkType: "margarita", isList: false, completion: { data in
self.parseJSONData(data)
})
}
}
extension DrinkViewController {
//MARK: JSON parser
fileprivate func parseJSONData(_ jsonData: Data?){
if let data = jsonData {
do {
let jsonDictionary = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers) as? [String : AnyObject]//Parses data into a dictionary
// print(jsonDictionary!)
if let drinkDictionary = jsonDictionary!["drinks"] as? [[String: Any]] {
for drink in drinkDictionary {
let drinkName = drink["strDrink"] as? String ?? ""
let catagory = drink["strCategory"] as? String
let drinkTypeIBA = drink["strIBA"] as? String
let alcoholicType = drink["strAlcoholic"] as? String
let glassType = drink["strGlass"] as? String
let drinkInstructions = drink["strInstructions"] as? String
let drinkThumbnailUrl = drink["strDrinkThumb"] as? String
let cocktailDrink = CocktailModel(drinkName: drinkName, catagory: catagory, drinkTypeIBA: drinkTypeIBA, alcoholicType: alcoholicType, glassType: glassType, drinkInstructions: drinkInstructions, drinkThumbnailUrl: drinkThumbnailUrl)
self.totalDrinksArray.append(cocktailDrink)
}
}
} catch let error as NSError {
print("Error: \(error.localizedDescription)")
}
}
DispatchQueue.main.async {
self.drinkTableView.reloadData()
}
}
//MARK: Image Downloader
func updateImage (imageUrl: String, onSucceed: #escaping () -> Void, onFailure: #escaping (_ error:NSError)-> Void){
//named imageData because this is the data to be used to get image, can be named anything
networkManager.downloadImage(imageUrl: imageUrl, onSucceed: { (imageData) in
if let image = UIImage(data: imageData) {
self.drinkImage = image
}
onSucceed()//must call completion handler
}) { (error) in
onFailure(error)
}
}
}
//MARK: Tableview Delegates
extension DrinkViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//return numberOfRows
return drinksPerPage
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = drinkTableView.dequeueReusableCell(withIdentifier: DRINK_CELL_REUSE_IDENTIFIER) as! DrinkCell
//get image from separate url
if let image = totalDrinksArray[indexPath.row].drinkThumbnailUrl{//index out of range error here
updateImage(imageUrl: image, onSucceed: {
if let currentImage = self.drinkImage{
DispatchQueue.main.async {
cell.drinkImage.image = currentImage
}
}
}, onFailure: { (error) in
print(error)
})
}
cell.drinkLabel.text = totalDrinksArray[indexPath.row].drinkName
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if let image = totalDrinksArray[indexPath.row].drinkThumbnailUrl{
updateImage(imageUrl: image, onSucceed: {
}, onFailure: { (error) in
print(error)
})
}
performSegue(withIdentifier: DRINK_SEGUE, sender: indexPath.row)
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let lastElement = drinksPerPage
if indexPath.row == lastElement {
self.drinkTableView.reloadData()
}
}
}
I saw this post: tableview-loading-more-cell-when-scroll-to-bottom and implemented the willDisplay function but am getting an "index out of range" error.
Can you tell me why you are doing this if you are getting all results at once then you don't have to limit your display since it is automatically managed by tableview. In tableview all the cells are reused so there will be no memory problem. UITableViewCell will be created when it will be shown.
So no need to limit the cell count.
I dont now what you are doing in your code but:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let lastElement = drinksPerPage // no need to write this line
if indexPath.row == lastElement { // if block will never be executed since indexPath.row is never equal to drinksPerPage.
// As indexPath starts from zero, So its value will never be 2.
self.drinkTableView.reloadData()
}
}
Your app may be crashing because may be you are getting only one item from server.
If you seriously want to load more then you can try this code:
Declare numberOfItem which should be equal to drinksPerPage
var numberOfItem = drinksPerPage
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//return numberOfRows
return numberOfItem
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if indexPath.row == numberOfItem - 1 {
if self.totalDrinksArray.count > numberOfItem {
let result = self.totalDrinksArray.count - numberOfItem
if result > drinksPerPage {
numberOfItem = numberOfItem + drinksPerPage
}
else {
numberOfItem = result
}
self.drinkTableView.reloadData()
}
}
}

Trying to implement GCD and concurrency on a table view

Currently using Alamofire synchronously within cellForRowAtIndexPath that queries a JSON array from Heroku, and within a for loop, creates a struct from each JSON object within the JSON array with image and text properties and then appends each struct in an array property within the table view controller. Not surprising that this is really slow. On app launch, the initial VC is a container VC that either shows a navigation controller or page VC based on if the user is "logged in." The initial VC in the page VC is a container VC that holds the table VC in question.
I'm totally new to GCD and the concept of concurrency. Was wondering how I can populate my array that serves as the foundational data for each of the table view cells.
Here's my current code - changing some variable names because I signed an NDA for this project:
import UIKit
import Alamofire
import Alamofire_Synchronous
final class PopularPearsTableViewController: UITableViewController {
let screenSize: CGRect = UIScreen.main.bounds
var pears: [Pear] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(PopularPearTableViewCell.self, forCellReuseIdentifier: "popularPear")
tableView.rowHeight = (screenSize.height) * 0.3
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
// MARK: - Table View Data Source
extension PopularShopsTableViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// just a stub, will be replaced with dynamic code later on
return 5
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print(#function)
let cell = tableView.dequeueReusableCell(withIdentifier: "popularPear", for: indexPath) as! PopularPearTableViewCell
let userDefaults = UserDefaults.standard
guard let pearUUID = userDefaults.string(forKey: "pearUUID"),
let pearToken = userDefaults.string(forKey: "pearToken")
else {
return cell
}
if indexPath.row == 0 {
let header = createAuthenticatedHeader(user: pearUUID, password: pearToken)
let pearResponse = Alamofire.request("url", headers: header).responseJSON()
if let pearsFromResponse = (pearResponse.result.value! as! JSON)["data"] as? [JSON] {
for pear in pearsFromResponse {
let name = pear["name"] as! String
let pictureURL = pear["picture_url"] as! String
let imageURL = URL(string: pictureURL)
let imageData = NSData(contentsOf: imageURL!)
let image = UIImage(data: imageData as! Data)!
let newPear = Pear(name: name, image: image)
self.pears.append(newPear)
}
}
}
cell.giveCell(pearImage: pears[indexPath.row].image, pearName: pears[indexPath.row].name)
return cell
}
}

Resources