2

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.

Aderbal Farias
  • 989
  • 10
  • 24
  • A few things I see... `tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "cell")` should be called only once and do it in the viewDidLoad. Put a print cell.name?.text = pizzas[indexPath.row].name and see if the pizzas[indexPath.row].name is what you expect. – Alan Scarpa Apr 04 '19 at 23:52
  • I have tried this before, its seems as though putting ```tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "cell")``` in the viewDidLoad or where I have it above, doesnt do the trick :/ – Austin Griffith Apr 05 '19 at 02:39
  • If there is anything else I can provide to further debug this problem please let me know as I am at a standstill – Austin Griffith Apr 05 '19 at 02:48
  • If the cell is designed as prototype cell in Interface Builder you **must not** register the cell. And why do you use `compactMap` although there is never a `nil` case? – vadian Apr 05 '19 at 03:51
  • I did design the cell in Interface builder, I will try taking out the register statement in the code. I previously had ```let inventory = rawInventory.flatMap``` but xocde presented a warning fix/change it to what I have; is that not the correct way to go about this? – Austin Griffith Apr 06 '19 at 20:29
  • @AlanScarpa I have tried taking out the ```tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "cell")``` from the viewDidLoad() method and I get the error as follow, pls reference image link. https://i.imgur.com/2BG4LJM.png https://i.imgur.com/zK2lPDl.png – Austin Griffith Apr 07 '19 at 20:45
  • In the storyboard, make sure that you've set your cell's class to PizzaTableViewCell – Alan Scarpa Apr 08 '19 at 22:42
  • And since you are doing this from the storyboard and with a prototype cell, you should remove `tableView.register` and follow these instructions: https://stackoverflow.com/a/22257583/3880396 – Alan Scarpa Apr 08 '19 at 22:45

1 Answers1

0

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.

  1. Please remove completely the register from tableView/cellForRow.

    // tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "cell")
    
  2. 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 enter image description here:

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

}

Konstantinos
  • 101
  • 1
  • 9
  • Im not exactly sure what you mean by the fetchInventory callback but I have tried putting the snippet you gave in the fetchInventory call in viewDidLoad method as well as at the end of the private func fetchInventory but that did not work :/ . If there is anything else I can provide to further debug this problem please let me know as I am at a standstill – Austin Griffith Apr 05 '19 at 02:45
  • @AustinGriffith It is the part of code inside brackets {} after fecthInventory in the beginning of your post that you pass it as parameter. – Konstantinos Apr 05 '19 at 05:09
  • I have tried this method and this does not solve the issues :/ – Austin Griffith Apr 06 '19 at 22:40
  • @AustinGriffith please check updated solution. Please note that I have mocked your API call. – Konstantinos Apr 06 '19 at 22:59
  • @Alan Scarpa I have verified that the ```cell.imageView?.image = pizzas[indexPath.row].image``` is working and setting the image okay but the text strings are not being set with ```cell.name?.text = pizzas[indexPath.row].name``` and ```cell.amount?.text = "$\(pizzas[indexPath.row].amount)" cell.miscellaneousText?.text = pizzas[indexPath.row].description``` . I have included screenshot to show it loading and the error printing nil in the console for ```print(cell.name?.text! as Any)```. https://i.imgur.com/PWjj9xf.png @Konstantinos – Austin Griffith Apr 07 '19 at 20:52
  • I wanted to also include screenshots of the storyboard connections. I have tested taking out the line ```tableView.register(PizzaTableViewCell.self, forCellReuseIdentifier: "cell")``` in the viewDidLoad method and it throws and error? https://imgur.com/a/cR626xE – Austin Griffith Apr 07 '19 at 20:56
  • Be careful. cell.imageView is reserved for one of the default cell types. That is why it is working. But, you need to have consistent call and use your outlet name. You have created a custom type didn't you? I don't have the register in my viewDidLoad. Please remove it completely and retry. – Konstantinos Apr 07 '19 at 21:40
  • I removed the register table view line and keep getting Thread 1: signal SIGABRT error upon loading the app in simulator. In the console it prints "*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier cell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'. I double checked and in the storyboard interface builder, when selecting the cell in the table view, the cell style is Custom and the identifier is "cell". – Austin Griffith Apr 07 '19 at 23:13
  • I have also tested setting the image in the cell with ```cell.pizzaImageView?.image = pizzas[indexPath.row].image``` but when running the app the table view just load blank cells. No text nor images loads. Im sorry for all the back and forth but I am new to xocde and seem to be having weird issues. – Austin Griffith Apr 07 '19 at 23:13
  • Can you confirm that in interface builder table view cell you have set your custom class name PizzaTableViewCell ? And did you have connected your cell class outlets ? – Konstantinos Apr 08 '19 at 07:38
  • Yes I have for sure set the custom class name, ```PizzaTableViewCell``` in the Interface builder for the cell in the table view. I have made the cell identifier name "cell" in the interface builder and double checked it matches in the code. I also have checked all the IBOutlets in in the ```PizzaTableViewCell``` class and they are indeed connected to the labels in the Storyboard. I have referenced here in a screenshot. https://imgur.com/tiUR0Tf Im not sure what else could be the issue at this point. – Austin Griffith Apr 08 '19 at 15:30
  • Can you provide xcode and swift versions? And can you as a sanity test just copy "Cell" identifier again into both your code and interface builder and retry? – Konstantinos Apr 14 '19 at 22:55
  • And please after entering the name "Cell" in interface builder press enter. Also, please check for and remove extra spaces at the end if any. – Konstantinos Apr 14 '19 at 23:17