2

I am new to swift programming and running into errors with performing a segue from a tableview cell when it is pressed to a view controller giving details about that cell. The error I am getting is:

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 
'Receiver (<DrinkupClient.DrinkListTableViewController: 0x7fec5d431510>) 
has no segue with identifier 'pizzaSegue''

I have already tried the following: 1) Tried renaming the storyboard and make sure to set the main storyboard in the project settings and in the info.plist file (Key is 'Main storyboard file base name'). I currently have the storyboard named: "Main.storyboard"

2) Tried doing a clean of the product (Product -> Clean) and rebuild but this gives same error

3) I have tried deleting the app from the simulator and running it again

4) I have double checked and the segue identifier in interface builder is called "pizzaSegue" and it is the same in my code.

import UIKit
import Alamofire

struct Drink {
    let id: String
    let name: String
    let description: String
    let amount: Float
    let image: UIImage

    init(data: [String: Any]) {
        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
    }
}

class DrinkTableViewCell: UITableViewCell {
    @IBOutlet weak var cellName: UILabel!
    @IBOutlet weak var cellAmount: UILabel!
    @IBOutlet weak var cellDescription: UILabel!
    @IBOutlet weak var cellImage: UIImageView!

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String!) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

class DrinkListTableViewController: UITableViewController {

    var drinks: [Drink] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        navigationItem.title = "Drink Selection"
        tableView.dataSource = self
        tableView.delegate = self
        //tableView.register(DrinkTableViewCell.self, forCellReuseIdentifier: "cell")

        tableView.register(DrinkTableViewCell.self as AnyClass, forCellReuseIdentifier: "cell")

        //tableView.register(UINib(nibName: "DrinkTableViewCell", bundle: Bundle.main), forCellReuseIdentifier: "cell")

        //tableView.estimatedRowHeight = 134
        //tableView.rowHeight = UITableView.automaticDimension

        fetchInventory { drinks in
            guard drinks != nil else { return }
            self.drinks = drinks!
            //print("Data from API call: ", self.drinks)
            //self.tableView.reloadData()
//            DispatchQueue.main.async { [weak self] in
//                self?.tableView.reloadData()
//            }
        }
    }

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        DispatchQueue.main.async { [weak self] in
            self?.tableView.reloadData()
        }
    }


    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
            performSegue(withIdentifier: "pizzaSegue", sender: self.drinks[indexPath.row] as Drink)
        //trying another method below?
        //self.navigationController?.pushViewController(UIViewController() as! PizzaViewController, animated: true)
    }



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

    private func fetchInventory(completion: @escaping ([Drink]?) -> 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 -> Drink? in
                var data = pizzaDict!
                data["image"] = UIImage(named: pizzaDict!["image"] as! String)
                //print("Printing each item: ", Drink(data: data))
                //printing all inventory successful
                return Drink(data: data)
            }
            completion(inventory)
    }
}

    // MARK: - Table view data source

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        print("ROWS: ", drinks.count)
        return drinks.count
    }


    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

        //let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! DrinkTableViewCell

        //let cell = UITableViewCell(style: UITableViewCell.CellStyle.subtitle, reuseIdentifier: "cell")

        let cell:DrinkTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "cell") as! DrinkTableViewCell

        //cell.cellName?.text = drinks[indexPath.row].name
        //cell.cellAmount?.text = String(drinks[indexPath.row].amount)
        //cell.cellDescription?.text = drinks[indexPath.row].description
        //cell.cellImage?.image = drinks[indexPath.row].image

        cell.imageView?.image = drinks[indexPath.row].image
        cell.textLabel?.text = drinks[indexPath.row].name
        cell.detailTextLabel?.text = drinks[indexPath.row].description

        //print(cell.textLabel?.text)
        //print(cell.detailTextLabel?.text)

        print(cell.cellName?.text as Any)
        //print(cell.cellImage?.image)
        return cell
    }

     override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
     return 100.0
     }

}

Screenshot showing error in console and the segue identifier in storyboard

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • @matt you are correct, DrinkListTableViewController is a file I created. Is this not the proper way to do this? I have assigned the class in storyboard attributes to the name of this class, what else do I need/not need to do?? – Austin Griffith Apr 11 '19 at 01:50
  • @matt From my understanding the Drink Selection title in the simulator is coming from the line ```navigationItem.title = "Drink Selection"``` in the viewDidLoad method in DrinkListTableViewController. The segue I want to perform is from the tableViewCell when pressed to the PizzaViewController. I have given the storyboard segue as ```pizzaSegue``` and made sure it matches the code, pls see above code. This is why I do not understand this error in the console! – Austin Griffith Apr 11 '19 at 02:12
  • @matt Okay I am still very confused, can I provide anything else to help you solve this issue I am having? – Austin Griffith Apr 11 '19 at 02:45
  • 1
    Well you could answer this. I see no segue or entry point arrow coming _into_ the navigation controller in your screen shot. So how would this navigation controller and its root view controller ever get instantiated and appear in the interface? Why do you believe the table view in the Simulator is the Root View Controller table view in the storyboard? – matt Apr 11 '19 at 02:49
  • @matt here is the full screenshot of my storyboard as it sits right now ! [screenshot of storyboard](https://imgur.com/tiUR0Tf) – Austin Griffith Apr 11 '19 at 02:49
  • @matt the entry to the tableView happens in code from my tabBarController which is how I have setup sign-in methods with facebook and email/pass. I have a button in the tabBarController that presents the tableView and this is working fine. ```let drinkController = DrinkListTableViewController()``` ```let drinkNavigationController = UINavigationController(rootViewController: drinkController)``` ```self.present(drinkNavigationController, animated: true, completion: nil)``` – Austin Griffith Apr 11 '19 at 02:52
  • @matt thank you so much for pointing that out! I really would have never figured that out with my basic knowledge! But I am still trying to fix this – Austin Griffith Apr 11 '19 at 03:15

1 Answers1

2

From your comment:

. I have a button in the tabBarController that presents the tableView and this is working fine.

let drinkController = DrinkListTableViewController() 
let drinkNavigationController = UINavigationController(rootViewController: drinkController) 
self.present(drinkNavigationController, animated: true, completion: nil)

No it isn’t working fine. It is the problem.

Basically this is the same situation as in my answer here:

https://stackoverflow.com/a/40077530/341994

You are obtaining a useless instance when you say DrinkListTableViewController(). What you need to do is talk to the storyboard and ask it to instantiate the desired view controller (by identifier) so that you get the instance from the storyboard, the one that has the segue.

matt
  • 515,959
  • 87
  • 875
  • 1,141
  • 1
    You’re right! It is `nil`, and the app will crash. +1 – Mannopson Apr 11 '19 at 03:12
  • @matt Okay so I am using ```let drinkController = instantiateViewController(withIdentifier:)``` . For the withIdentifer, do I need to go to the viewController in the storyboard and give it a storyboard id? Or where do you create that identifier! – Austin Griffith Apr 11 '19 at 03:14
  • 1
    Yes, obviously you need to give it an identifier in the storyboard just so you can do this, as I said in the other answer I linked to. – matt Apr 11 '19 at 03:19
  • 1
    I turned this question-answer into a blog post, because it's such a common mistake: http://www.programmingios.net/dont-make-a-new-instance-by-mistake-2/ – matt Apr 12 '19 at 17:13