Issues loading data into UITableView from API call - ios

I am new to ios development and running into issue loading data from an API call to a UITableView. The app is going to serve as ordering system. To get data into each cell of the tableView I have a button on a viewController that sends a post request with the id of the item and the id is matched to items in memory on the server. I have a view controller that has a tableView of the current ordered items which is where my problem is.
I have two other view controllers (BeerListTableViewController and CocktailListTableViewController) with tableViews in them loaded with the items that you can order. These tableViews are loading correctly with their respective items. When pressing on a cell in either tableView I perform a segue to another viewController (BeerDetailViewController or CocktailDetailViewController) where the item is shown and an "Place Order" button is shown. The place order button sends the item to the server where it is added to the array of orders and then popViewController to get back to the tableView. I have verified that the items are getting added correctly. In both BeerListTableViewController and CocktailListTableViewController I have added a barButtonItem "Orders" which segues to DrinkOrderTableView and this is where my ISSUE lies. In this tableView only the orders from BeerDetailViewController are showing. I have included code from the xcode project and my node index.js file. Any help is much appreciated!
class DrinkOrdersTableViewController: UITableViewController {
var orders: [Order] = []
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Current Orders"
fetchAllCurrentOrders { orders in
self.orders = orders!
print(self.orders)
self.tableView.reloadData()
}
}
private func fetchAllCurrentOrders(completion: #escaping([Order]?) -> Void) {
Alamofire.request("http://127.0.0.1:4000/orders", 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 currentOrders = rawInventory.compactMap { ordersDict -> Order? in
guard let orderId = ordersDict!["id"] as? String,
let orderStatus = ordersDict!["status"] as? String,
var pizza = ordersDict!["pizza"] as? [String: Any] else { return nil }
pizza["image"] = UIImage(named: pizza["image"] as! String)
return Order(
id: orderId,
pizza: Pizza(data: pizza),
status: OrderStatus(rawValue: orderStatus)!
)
}
completion(currentOrders)
}
}
#IBAction func closeButtonPressed(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Debugging ROWS", orders.count)
return orders.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "order", for: indexPath)
let order = orders[indexPath.row]
cell.textLabel?.text = order.pizza.name
cell.imageView?.image = order.pizza.image
cell.detailTextLabel?.text = "$\(order.pizza.amount) - \(order.status.rawValue)"
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "orderSegue", sender: orders[indexPath.row] as Order)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "orderSegue" {
guard let vc = segue.destination as? OrderViewController else { return }
vc.order = sender as? Order
}
}
}
link to server code
Link to screencap of issue

Related

Duplicate cells on each load of tableView from coredata

The view shows duplicate rows for each record in CoreData which keep multiplying on each reload. The code is as under.
What happen is whenever I add record then I view record it shows me the record. Then I click back for the homepage after that when I click on view record I see the copy of same record. So now I have 2 same records. Can anyone please help me with and I think the problem is in table view so here is my table view controller code
import UIKit
import CoreData
var Rec = [Records]()
class TableViewController: UITableViewController {
var firstLoad = true
func nondel() -> [Records]
{
var nodellist = [Records]()
for note in Rec
{
if(note.del == nil)
{
nodellist.append(note)
}
}
return nodellist
}
override func viewDidLoad() {
super.viewDidLoad()
if(firstLoad)
{
firstLoad = false
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context:NSManagedObjectContext = appDelegate.persistentContainer.viewContext
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "Records")
do{
let results: NSArray = try context.fetch(request) as NSArray
for result in results {
let note = result as! Records
Rec.append(note)
}
}
catch
{
print("Fetch Failed")
}
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell") as! TableViewCell
let thisrec: Records!
thisrec = nondel()[indexPath.row]
cell.idLB.text = thisrec.id
cell.nameLB.text = thisrec.name
cell.lastLB.text = thisrec.last
cell.genderLB.text = thisrec.gender
cell.ageLB.text = thisrec.age
cell.addressLB.text = thisrec.address
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return nondel().count
}
override func viewDidAppear(_ animated: Bool) {
tableView.reloadData()
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath){
self.performSegue(withIdentifier: "editNote", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if(segue.identifier == "editNote")
{
let indexPath = tableView.indexPathForSelectedRow!
let recDetail = segue.destination as? AddViewController
let selectedCell: Records!
selectedCell = nondel()[indexPath.row]
recDetail!.selectedCell = selectedCell
tableView.deselectRow(at: indexPath, animated: true)
}
}
}
Your code is unbelievable cumbersome.
First of all never declare a data source outside of any class.
Second of all never use a function to build an array as table view data source.
Third of all firstRun is pointless because viewDidLoad is called only once anyway.
Fourth of all rather than filtering the received records manually apply a predicate to the fetch request
Further it's highly recommended to name Core Data entities always in singular form (Record) and to use the specific generic fetch request of this entity.
class TableViewController: UITableViewController {
var records = [Record]()
override func viewDidLoad() {
super.viewDidLoad()
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let context = appDelegate.persistentContainer.viewContext
let request : NSFetchRequest<Record> = Record.fetchRequest()
request.predicate = NSPredicate(format: "del != nil")
do {
records = try context.fetch(request)
} catch { print("Fetch Failed", error) }
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell") as! TableViewCell
let thisrec = records[indexPath.row]
cell.idLB.text = thisrec.id
cell.nameLB.text = thisrec.name
cell.lastLB.text = thisrec.last
cell.genderLB.text = thisrec.gender
cell.ageLB.text = thisrec.age
cell.addressLB.text = thisrec.address
return cell
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return records.count
}
...

Swift Tableview Load Data from Firebase Database

I am currently trying to display data from my real-time database of Firebase in different tableviews.
In my first tableview I load my first level of my database structure (this already works).
Now I want to see what the user selects in the first tableview, and then display the next level of my database in a new TableView.
I have a function in my second TableViewController.swift file where to save in the selected row from the first TableView.
This way I want to save the next level from my database into an array so that this data will be displayed in my second tableview. When I then debug my new array, I also see the correct data in the new array. However, the data is not displayed in the second TableView.
I guess it's because the data is not 100% ready before the TableView loads.
Do you have a tip?
Firebase Structure:
-sports
-Bicycle
-BMX
-Bike 1
-img: „bike1.png“
-text: „bike 1“
-Bike 2
-img: „bike2.png“
-text: „bike 1“
-Car
-Audi
-Car 1
-img: „car1.png“
-text: „car 1“
-Car 2
-img: „car2.png“
-text: „car 2“
FirstTableViewController:
import UIKit
import Firebase
class FirstTableViewController: UITableViewController {
var categorie = [String]()
func loadData() {
var ref: DatabaseReference!
ref = Database.database().reference()
ref.child("sports").observeSingleEvent(of: .value, with: { snapshot in
if let sports = snapshot.value as? [String: Any] {
for (title, _) in sports {
self.categorie.append(title)
self.tableView.reloadData()
}
}
})
}
override func viewDidLoad() {
loadData()
super.viewDidLoad()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categorie.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "sportCell")
cell?.textLabel?.text = categorie[indexPath.row]
return cell!
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Set the segue's identifier in the Storyboard
performSegue(withIdentifier: "firstToSecond", sender: self)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "firstToSecond" {
guard let destination = segue.destination as? SecondTableViewController,
let indexPath = tableView.indexPathForSelectedRow else { return }
destination.detailedValue = categorie[indexPath.row]
}
}
}
SecondTableViewController:
import UIKit
import Firebase
class SecondTableViewController: UITableViewController {
var detailedValue: String?
var secondArray = [String]()
func setIndex(value: String) {
loadData(index: value)
}
func loadData(index: String) {
var ref: DatabaseReference!
ref = Database.database().reference()
if (index != "") {
ref.child("sports").child(index).observeSingleEvent(of: .value, with: { snapshot in
if let sports = snapshot.value as? [String: Any] {
for (title, _) in sports {
self.secondArray.append(title)
self.tableView.reloadData()
}
}
})
}
}
override func viewDidLoad() {
super.viewDidLoad()
if let detailedValue = detailedValue {
loadData(index: detailedValue)
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return secondArray.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "sorteCell")
cell?.textLabel?.text = secondArray[indexPath.row]
return cell!
}
}
UPDATE 1:
Thanks to #Jay Lee for the above code.
UPDATE 2:
You are not loading the data to the SecondTableViewController instance that is presented on your screen, but to a new SecondTableViewController instance that you created in the func tableView(_:=,cellForRowAt:) method in your FirstTableViewController.
The logs are printed from the multiple instances you created from it.
This is not what you want, as you are creating multiple SecondTableViewController instances every time a new cell shows in your FirstTableViewController.
You should rather get a reference to the actual SecondTableViewController that is presented and supply the data it.
If you are using a storyboard, you can use prepare(for:sender:) to do that.
We have two choices: provide the entire data from the FirstTableViewController to SecondTableViewController using a delegate design pattern, or just provide value to SecondTableViewController and leave the fetching to it.
Based on your code, you can just supply the SecondTableViewController with value that your setIndex(value:) method in the SecondTableViewController uses, and get the data after the SecondTableViewController loads.
For example, in your SecondTableViewController:
class SecondTableViewController: UITableViewController {
...
var detailedValue: String?
override func viewDidLoad() {
super.viewDidLoad()
if let detailedValue = detailedValue {
setIndex(value: detailedValue)
}
}
...
}
and in your FirstTableViewController:
class FirstTableViewController: UITableViewController {
...
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Set the segue's identifier in the Storyboard
performSegue(withIdentifier: "yourIdentifier", sender: self)
}
...
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourIdentifier" {
guard let destination = segue.destination as? SecondTableViewController,
let indexPath = tableView.indexPathForSelectedRow else { return }
destination.detailedValue = categories[indexPath.row]
}
}
}
But note that you already have a data to be shown on SecondTableViewController in your FirstTableViewController, so you should probably make a protocol and set FirstTableViewController its delegate.
EDIT:
Your segue should not be connected like this:
but like this:

Issue loading data from array to UITableView cells

I am very new to swift programming and trying to build an app to take orders and relay them to an admin app. My data is not loading in my UITableView and I'm not sure why, as far as I can tell I've done everything by the book. I am loading data from a node server I created and when printing the contents of the array all items are printed as key,pair values. The UIimages are loading in each of the tableView cells but the labels are not and after setting the labels and printing them, the values are still nil of the labels.
I created a TableView class called PizzaListTableViewController and a custom TableViewCell class called PizzaTableViewCell. I have added a UIimage and three labels in the storyboard interface builder.
Structure is: ViewController > TableView > TableViewCell > Image, Labels
My main VC is connected to its ViewController.class
My TableViewCell is connected to its TableViewCell.class
I have an identifier and linked it up, as per code below
I linked all the outlets. Any help would be greatly appreciated!
I have tried to rewrite the classes, break all outlet connections and reconnect them, assign values in the method where the labels are set but no luck with anything.
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.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)
}
} //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 all data: ", Pizza(data: data))
//printing all inventory successful
return Pizza(data: data)
}
//self.tableView.reloadData()
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
}
//THIS IS WHERE THE CELL IDENTIFIER IS ??
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//print("IN CELLFORROWAT")
tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "cell")
let cell: PizzaTableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! PizzaTableViewCell
//cell.backgroundColor = Services.baseColor
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)
//print("END CELLFORROWAT")
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: "pizza", sender: self.pizzas[indexPath.row] as Pizza)
} //END OF override func tableView
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "pizza" {
guard let vc = segue.destination as? PizzaViewController else { return }
vc.pizza = sender as? Pizza
}
} //END OF override preppare func
}
class PizzaTableViewCell: UITableViewCell {
#IBOutlet weak var name: UILabel!
#IBOutlet weak var pizzaImageView: UIImageView!
#IBOutlet weak var amount: UILabel!
#IBOutlet weak var miscellaneousText: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
//Configure the view for the selected state
}
}
struct Pizza {
let id: String
let name: String
let description: String
let amount: Float
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
}
}
I have also printed values of the array to console and the data is printing as expected but values of cell.name?.text, cell.amount?.text, and cell.miscellaneousText?.text print nil.
Please try to reload your tableview in Main thread inside the code that you pass as a parameter to fetchInventory:
DispatchQueue.main.async {
self.tableView.reloadData()
}
So, your fetchInventory call should become:
fetchInventory { pizzas in
guard pizzas != nil else { return }
self.pizzas = pizzas!
//print(self.pizzas)
DispatchQueue.main.async {
self.tableView.reloadData()
}
//print(self.pizzas)
}
Please avoid to do UI work from a background thread because it is not correct/safe. Also, you may try to set self?.pizzas too inside that main thread block.
And please take into account Alan's advice on double call.
Please remove completely the register from tableView/cellForRow.
// tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "cell")
Instead of:
cell.imageView?.image = pizzas[indexPath.row].image
put:
cell.pizzaImageView?.image = pizzas[indexPath.row].image
This is your outlet name.
Please check my test below that is working :
import UIKit
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.estimatedRowHeight = 134
//tableView.rowHeight = UITableViewAutomaticDimension
fetchInventory { pizzas in
guard pizzas != nil else { return }
self.pizzas = pizzas!
print(self.pizzas)
DispatchQueue.main.async {
self.tableView.reloadData()
}
//print(self.pizzas)
}
} //end of viewDidLoad
private func fetchInventory(completion: #escaping ([Pizza]?) -> Void) {
let rawInventory0 = [
[
"id": "1",
"name": "name1",
"amount": 1234,
"description": "description1",
"image": "image1"
],
[
"id": "2",
"name": "name2",
"amount": 1235,
"description": "description2",
"image": "image2"
],
[
"id": "3",
"name": "name3",
"amount": 1236,
"description": "description3",
"image": "image3"
],
[
"id": "4",
"name": "name4",
"amount": 1237,
"description": "description4",
"image": "image4"
]
] as? [[String: Any]?]
guard let rawInventory1 = rawInventory0 as? [[String: Any]?] else { return completion(nil) }
let inventory = rawInventory1.compactMap { pizzaDict -> Pizza? in
var data = pizzaDict!
data["image"] = UIImage(named: pizzaDict!["image"] as! String)
print(data)
print("CHECK")
print("Printing all data: ", Pizza(data: data))
//printing all inventory successful
return Pizza(data: data)
}
//self.tableView.reloadData()
completion(inventory)
}
// MARK: - Table view data source
#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
}
//THIS IS WHERE THE CELL IDENTIFIER IS ??
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//print("IN CELLFORROWAT")
// tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "cell")
let cell: PizzaTableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! PizzaTableViewCell
//cell.backgroundColor = Services.baseColor
cell.name?.text = pizzas[indexPath.row].name
cell.pizzaImageView?.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)
//print("END CELLFORROWAT")
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: "pizza", sender: self.pizzas[indexPath.row] as Pizza)
} //END OF override func tableView
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "pizza" {
guard let vc = segue.destination as? PizzaViewController else { return }
vc.pizza = sender as? Pizza
}
} //END OF override preppare func
}

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.

View doesn't get updated until the second time loaded

I have a main view which is a table view with a list of countries. When clicking on any country name (cell), another view is loaded via segue which is passing the name of the country to the next view controller's navigation bar title.
The problem is on the first click the title isn't updated, but when I click back button (dismissing the current view) and click on another country name, the second view loads again and shows the previous title that was suppose to be shown on the first attempt.
The code for the first main view controller:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var sectionsArray = [String]()
var sectionsCountries = [Array<AnyObject>]()
#IBOutlet weak var countries: UITableView!
internal func numberOfSections(in tableView: UITableView) -> Int {
// Return the number of sections.
return self.sectionsArray.count
}
internal func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of rows in the section.
return self.sectionsCountries[section].count
}
internal func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.sectionsArray[section]
}
internal func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CountryCell", for: indexPath)
cell.textLabel?.text = self.sectionsCountries[indexPath.section][indexPath.row] as? String
return cell
}
var valueToPass:String!
internal func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You selected cell #\(indexPath.row)!")
// Get Cell Label
let indexPath = tableView.indexPathForSelectedRow;
let currentCell = tableView.cellForRow(at: indexPath!) as UITableViewCell!;
valueToPass = currentCell?.textLabel?.text
performSegue(withIdentifier: "cellSegue", sender: self)
//print(valueToPass)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "cellSegue" {
let destination = segue.destination as! CountryViewController
destination.passedValue = valueToPass
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let url = URL(string: "http://cyber7.co.il/swift/countries/countries-list.json")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error)
} else {
if let urlContent = data {
do {
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableContainers)
for result in jsonResult as! [Dictionary<String, AnyObject>]{
self.sectionsArray.append(result["sectionName"] as! String)
self.sectionsCountries.append(result["sectionCountries"] as! Array<String> as [AnyObject])
}
} catch {
print("JSON Processing Failed")
}
DispatchQueue.main.async(execute: { () -> Void in
self.countries.reloadData()
})
}
}
}
task.resume()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
the code for the second view controller:
import UIKit
class CountryViewController: UIViewController {
var passedValue:String!
#IBOutlet weak var navBar: UINavigationBar!
#IBAction func backButton(_ sender: AnyObject) {
self.dismiss(animated: true, completion: nil)
}
override func viewWillAppear(_ animated: Bool) {
self.navBar.topItem?.title = passedValue
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
When you have a segue set up from a table view cell to a view controller in the storyboard then it is performed automatically when a cell is selected. Your call to perform a segue in your cell selection method is performing the segue a second time after the first one has already been performed.
Remove your tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) method and do all the data passing logic in prepareForSegue:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "cellSegue" {
let destination = segue.destination as! CountryViewController
let indexPath = countries.indexPathForSelectedRow
let currentCell = countries.cellForRow(at: indexPath!)
destination.passedValue = currentCell?.textLabel?.text
}
}

Resources