Cannot invoke 'indexPathForSelectedRow' with no arguments

I am trying to create a "simple" app that allows me to look up certain vehicles and gives me relevant information about it.


I have been following a video made by someone creating an app showing how to move info from one tableview to another, but despite my best efforts, I keep getting "Cannot invoke 'indexPathForSelectedRow' with no arguments" error for the following line:


var indexPath: NSIndexPath = self.tableView.indexPathForSelectedRow()!


The video is a couple of years old, so I believe the video maker may have an older version of Xcode and or Swift, but it seems so close?


The issue is in the first tableview controller file, I have included the second tableview controller file and tableview table in case either is causing the issue.


Can anyone tell me what is going wrong?



FIRST TABLEVIEW CONTROLLER:


import Foundation

import UIKit


class VehicleMakeController: UITableViewController {


var VehicleMakeArray = [String] ()


var VehicleModelArray = [VehicleModelTable] ()


override func viewDidLoad() {

super.viewDidLoad()


// Do any additional setup after loading the view.


VehicleMakeArray = ["Ford", "Volkeswagon", "Peugeot"]


VehicleModelArray = [VehicleModelTable(SecondTitle: ["Mondeo", "Focus", "Fiesta"]), VehicleModelTable(SecondTitle: ["Caddy", "Golf", "Passat"]), VehicleModelTable(SecondTitle: ["Partner", "208", "106"])]


}


override func didReceiveMemoryWarning() {

super.didReceiveMemoryWarning()

// Dispose of any resources that can be recreated.

}


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

return 1

}


override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

return VehicleMakeArray.count

}


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


let cell = self.tableView.dequeueReusableCell(withIdentifier: "VehicleMakeCell", for: indexPath) as UITableViewCell


// Configure the cell...


cell.textLabel?.text = VehicleMakeArray [indexPath.row]


return cell

}


override func prepare(for segue: UIStoryboardSegue, sender: Any?) {


var indexPath: NSIndexPath = self.tableView.indexPathForSelectedRow()!


var DestViewController = segue.destination as! VehicleModelController


var VehicleModelArrayTwo: VehicleModelTable


VehicleModelArrayTwo = VehicleModelArray[indexPath.row]


DestViewController.VehicleModelArray = VehicleModelArrayTwo.SecondTitle


}


SECOND TABLEVIEW CONTROLLER:


import Foundation

import UIKit


class VehicleModelController: UITableViewController {


var VehicleModelArray = [String] ()


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.

}


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

return 1

}


func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

return VehicleModelArray.count

}


func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {


let Cell = self.tableView.dequeueReusableCell(withIdentifier: "VehicleModelCell", for: indexPath as IndexPath) as UITableViewCell


// Configure the cell...


Cell.textLabel?.text = VehicleModelArray [indexPath.row]


return Cell

}

}



SECOND TABLEVIEW TABLE


import Foundation


struct VehicleModelTable {


var SecondTitle: [String]


}

Accepted Reply

Another advice for your code.

You have 2 arrays, for makers and for their models.

With you present data model, you need to make sure they are exactly in the same order. When you have 100 auto makers, that may become hard to check.


A better way would be to define a struct with the make and its models.

Note: one could think of creating a dictionary, with make as key and models as values, but dictionaries are not ordered, hence causing new problems.

struct VehiclesOfMake {
    var make: String
    var models: [String]
}


You should adapt some namings to reflect this new data model.

Code for VehicleMakeController would now become (hopefully) easier to maintain and even understand:


class VehicleMakeController: UITableViewController {
  
    var vehiclesByMake = [VehiclesOfMake]()
  
    override func viewDidLoad() {
        super.viewDidLoad()
      
        // Do any additional setup after loading the view.
        vehiclesByMake = [VehiclesOfMake(make: "Ford", models: ["Mondeo", "Focus", "Fiesta"]),
                          VehiclesOfMake(make: "Volkeswagon", models: ["Caddy", "Golf", "Passat"]),
                          VehiclesOfMake(make: "Peugeot", models: ["Partner", "208", "106"])
        ]      
    }
  
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
  
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
  
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return vehiclesByMake.count      //  vehicleMakeArray.count
    }
  
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
      
        let cell = self.tableView.dequeueReusableCell(withIdentifier: "VehicleMakeCell", for: indexPath) as UITableViewCell
      
        // Configure the cell...
      
        cell.textLabel?.text = vehiclesByMake[indexPath.row].make //  vehicleMakeArray [indexPath.row]
      
        return cell
    }
  
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
      
        let row = self.tableView?.indexPathForSelectedRow?.row ?? 0
        let destViewController = segue.destination as! VehicleModelController
      
        destViewController.vehicleModelArray = vehiclesByMake[row].models //  vehicleModelArrayTwo.secondTitle
      
    }
  
}

Replies

There are many points to correct.

Read carefully the remarks, implement the changes.

If it works, thanks to close the thread.

If not, tell what is exactly the problem.


1. In prepare, in your code:


you use NSIndexPath, should use IndexPath

indexPathForSelectedRow is now a property, not a func: no ()

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        var indexPath: NSIndexPath = self.tableView.indexPathForSelectedRow()!
        var DestViewController = segue.destination as! VehicleModelController
        var VehicleModelArrayTwo: VehicleModelTable
        VehicleModelArrayTwo = VehicleModelArray[indexPath.row]
        DestViewController.VehicleModelArray = VehicleModelArrayTwo.SecondTitle
    }


So change like this

Line 2, replace by:

        let indexPathZero = IndexPath(item: 0, section: 0)
        let indexPath: IndexPath = self.tableView?.indexPathForSelectedRow ?? indexPathZero    //indexPathForSelectedRow!

If you want to use NSIndexPath (not recommended), you should write

        let indexPathZero = NSIndexPath(item: 0, section: 0)
        let indexPath: NSIndexPath = self.tableView?.indexPathForSelectedRow as NSIndexPath? ?? indexPathZero


2. You dont use in fact the indexPath, but just the row.


You can thus simplify by writing:

        let row = self.tableView?.indexPathForSelectedRow?.row ?? 0

and call later

       vehicleModelArrayTwo = VehicleModelArray[row] // instead of indexPath.row]



3. Giving var names starting with Uppercase is very confusing. You should change it.

such as VehicleModelArrayTwo to become vehicleModelArrayTwo, SecondTitle to become secondTitle


4 In SECOND TABLEVIEW CONTROLLER, your delegate func signatures for tableView are wrong


        func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

must become

        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {


      func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

must become

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

override, start with underscore _, IndexPath instead of NSIndexPath


So the complete code looks like:


import Foundation
import UIKit

class VehicleMakeController: UITableViewController {
   
    var vehicleMakeArray = [String] ()
    var vehicleModelArray = [VehicleModelTable] ()
   
    override func viewDidLoad() {
        super.viewDidLoad()
       
        // Do any additional setup after loading the view.
        vehicleMakeArray = ["Ford", "Volkeswagon", "Peugeot"]
        vehicleModelArray = [VehicleModelTable(secondTitle: ["Mondeo", "Focus", "Fiesta"]), VehicleModelTable(secondTitle: ["Caddy", "Golf", "Passat"]), VehicleModelTable(secondTitle: ["Partner", "208", "106"])]
       
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

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

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return vehicleMakeArray.count
    }

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

        let cell = self.tableView.dequeueReusableCell(withIdentifier: "VehicleMakeCell", for: indexPath) as UITableViewCell

        // Configure the cell...

        cell.textLabel?.text = vehicleMakeArray [indexPath.row]

        return cell
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {

        let row = self.tableView?.indexPathForSelectedRow?.row ?? 0
        let destViewController = segue.destination as! VehicleModelController

        var vehicleModelArrayTwo: VehicleModelTable
        vehicleModelArrayTwo = vehicleModelArray[row] // indexPath.row]
        destViewController.vehicleModelArray = vehicleModelArrayTwo.secondTitle

    }
   
}

// SECOND TABLEVIEW CONTROLLER:


import Foundation
import UIKit

    class VehicleModelController: UITableViewController {
       
        var vehicleModelArray = [String] ()
       
        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.
        }
       
        override func numberOfSections(in tableView: UITableView) -> Int {
            return 1
        }
       
//        func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return vehicleModelArray.count
        }
       
//        func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
           
            let Cell = self.tableView.dequeueReusableCell(withIdentifier: "VehicleModelCell", for: indexPath as IndexPath) as UITableViewCell
           
            // Configure the cell...
           
            Cell.textLabel?.text = vehicleModelArray [indexPath.row]
           
            return Cell
        }
    }


// SECOND TABLEVIEW TABLE


import Foundation

struct VehicleModelTable {

    var secondTitle: [String]

}

Another advice for your code.

You have 2 arrays, for makers and for their models.

With you present data model, you need to make sure they are exactly in the same order. When you have 100 auto makers, that may become hard to check.


A better way would be to define a struct with the make and its models.

Note: one could think of creating a dictionary, with make as key and models as values, but dictionaries are not ordered, hence causing new problems.

struct VehiclesOfMake {
    var make: String
    var models: [String]
}


You should adapt some namings to reflect this new data model.

Code for VehicleMakeController would now become (hopefully) easier to maintain and even understand:


class VehicleMakeController: UITableViewController {
  
    var vehiclesByMake = [VehiclesOfMake]()
  
    override func viewDidLoad() {
        super.viewDidLoad()
      
        // Do any additional setup after loading the view.
        vehiclesByMake = [VehiclesOfMake(make: "Ford", models: ["Mondeo", "Focus", "Fiesta"]),
                          VehiclesOfMake(make: "Volkeswagon", models: ["Caddy", "Golf", "Passat"]),
                          VehiclesOfMake(make: "Peugeot", models: ["Partner", "208", "106"])
        ]      
    }
  
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
  
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
  
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return vehiclesByMake.count      //  vehicleMakeArray.count
    }
  
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
      
        let cell = self.tableView.dequeueReusableCell(withIdentifier: "VehicleMakeCell", for: indexPath) as UITableViewCell
      
        // Configure the cell...
      
        cell.textLabel?.text = vehiclesByMake[indexPath.row].make //  vehicleMakeArray [indexPath.row]
      
        return cell
    }
  
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
      
        let row = self.tableView?.indexPathForSelectedRow?.row ?? 0
        let destViewController = segue.destination as! VehicleModelController
      
        destViewController.vehicleModelArray = vehiclesByMake[row].models //  vehicleModelArrayTwo.secondTitle
      
    }
  
}

Many thanks!


Both of your suggestions have resolved my issues and achieve what I wanted, with the progress I have made so far within my storyboard.


Before I go on, it is probably worth mentioning (and probably obvious from my original code) that I am by no way in any stretch of the imagination, proficient in the writing of Swift code. A few years ago (iOS 8, Xcode 6), I began creating an app, in a purely "self taught" manner, downloading and reading any documents or videos that gave workable examples of what I was trying to achieve. Having read and worked through documents such as Techtopia's iOS 8 App Development Essentials, which started from installing Xcode and ended in submitting to the App Store (https://www.techotopia.com/index.php/IOS_8_App_Development_Essentials), I was able to teach myself a basic understanding of app development. However, after a lengthy period and hitting a brickwall with the passing of data between tableviews, I ceased trying to develop the app.


The app I have now decided to try to develop, is in theory a more "straight forward" app (my previous one was far too ambitious for my level of compitencey), but I have all but forgotten anything I used to know and a lot has changed since iOS 8 / Xcode 6. My original code came from me building the initial setup in storeyboard and then following a video on YouTube called "Passing data between tableviews using structs and arrays (https://www.youtube.com/watch?v=pR6dR-vVZeY&t=549shttps://www.youtube.com/watch?v=pR6dR-vVZeY&t=549s), which led me to the issue I posted above.


With all that said:


I am not planning to become an out and out app developer or code writer, but would like to try to achieve creating this current app. I have a few features in mind, but the basics are that the user will be able to select a vehicle based on make then model and then finally be shown an image of the vehicle.


Based on what you stated in your second piece of advice above, I believe this option (dictionary style) is what I will continue with, as you are correct in assuming that I will eventually want to build a large, possibly inexaustable list of vehicles. With this in mind, and thinking about what you said about dictionaries not keeping things ordered, I think I have seen somewhere that there is a way to code the list (dictionary) so that it keeps everything ordered as data is added to the list, is this the case, or am I always going to have to insert the data in a very specific way?

You could use key-value pairs:

https://developer.apple.com/documentation/swift/keyvaluepairs

Thanks,


Will look into that.