13

I'm encountering problems with my UITableViewCells. I connected my UITableView to a API to populate my cells.

Then I've created a function which grabs the indexPath.row to identify which JSON-object inside the array that should be sent to the RestaurantViewController.

Link to my Xcode Project for easier debugging and problem-solving

Here's how my small snippet looks for setting the "row-clicks" to a global variable.

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
     i = indexPath.row
}

And here's my prepareForSegue() function that should hook up my push-segue to the RestaurantViewController.

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
    if segue.identifier == "toRestaurant"{
    let navigationController = segue.destinationViewController as UINavigationController
    let vc = navigationController.topViewController as RestaurantViewController
    vc.data = currentResponse[i] as NSArray
 }
}

And here's how I've set up my segue from the UITableViewCell

Here's my result, I've tried to click every single one of these cells but I won't be pushed to another viewController...I also don't get an error. What is wrong here?

Tried solutions that won't work

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
        if segue.identifier == "toRestaurant"{
            let vc = segue.destinationViewController as RestaurantViewController
            //let vc = navigationController.topViewController as RestaurantViewController
            vc.data = currentResponse[i] as NSArray
        }
    }
Jack
  • 3,632
  • 7
  • 45
  • 67
  • Isn't the error came from the line: let navigationController = segue.destinationViewController as UINavigationController ? I think destinationController is not a navigationController, but the uiviewcontroller itself. – Dániel Nagy Feb 04 '15 at 14:39
  • @DánielNagy I commented out ``let vc = navigationController.topViewController as RestaurantViewController`` and edited the line above to have "RestaurantViewController". Compiled and run, no errors, just no response from clicking on the cells. – Jack Feb 04 '15 at 14:42
  • I just checked that the push segue is deprecated, I'm not sure, but what if you change that to Show (e.g. Push) ? – Dániel Nagy Feb 04 '15 at 14:48
  • Verify that the segue is triggered on cell selection (in the connections inspector of the cell) and not something else. – Starscream Feb 04 '15 at 14:50
  • @DánielNagy My choices on segue types are ``Push, Modal, Popover, Replace, Custom`` – Jack Feb 04 '15 at 14:57
  • @Jack Ok, then you probably not using XCode 6.1.1. – Dániel Nagy Feb 04 '15 at 14:58
  • @DánielNagy, I'm using Xcode version 6.1.1 (6A2008a) – Jack Feb 04 '15 at 14:59
  • @Starscream The segue I've created is the only event connected to my cell – Jack Feb 04 '15 at 15:01
  • What code are you using to create the cells? Are you using dequeueReusableCellWithIdentifier:forIndexPath with the correct reuse identifier? The cells in your image are not the same type as those in the prototype. The image seems to have cells with subheading where the prototype does not. Also odd you are not seeing the Adaptive segues. Sure its xcode 6? Check the About menu item in case you're running another version. – Rory McKinnel Feb 04 '15 at 15:26
  • @RoryMcKinnel I'm 100% sure that it's Xcode 6.1.1 running om my Mac. And I'm using ``cellForRowAtIndexPath``to create my cells, I have a function that grabs all JSON and then sets it to ``tableData``. Here's a complete gist om my [ViewController.swift](https://gist.github.com/jackbillstrom/217f1fdcd62b15437c72) – Jack Feb 04 '15 at 15:32
  • From your code your not creating the cell correctly. Use tableView.dequeueReusableCellWithIdentifier("MyTestCell", forIndexPath: indexPath) as UITableViewCell. This is why its not firing the segue. Otherwise you need to ask the storyboard to create you a cell. You just created one with an identifier, but its not linked up to a segue. – Rory McKinnel Feb 04 '15 at 15:39
  • I'd bet my right thumb's nail that the error comes from prepareForSegue, try commenting it and see what happens – martskins Feb 06 '15 at 14:59
  • @lascort I've commented it out now, it sends me to the correct view now. But then, how do I send data now? – Jack Feb 06 '15 at 15:26
  • @Jack are you sure it's going through the if statement? put an NSLog in there to find out and comment out everything else. – martskins Feb 06 '15 at 16:26
  • Hi @Jack, I am sad to see that you accepted an answer I sent over to you via chat a day earlier. – Tammo Freese Feb 12 '15 at 11:50
  • @TammoFreese I tried that solution, didn't work at all. I spoke to ``ergon``via chat aswell, the theoretical solution didn't work so he had to send me the Xcode project which contained the code that I've tried in my original project. – Jack Feb 13 '15 at 07:15
  • @Jack Maybe you have overlooked my answers in the chat :( : http://stackoverflow.com/questions/28323925/push-segue-from-uitableviewcell-to-viewcontroller-in-swift?noredirect=1#comment45308706_28323925 – Tammo Freese Feb 13 '15 at 11:18

9 Answers9

22

The problem is that you're not handling your data correctly. If you look into your currentResponse Array, you'll see that it holds NSDictionaries but in your prepareForSegue you try to cast a NSDictionary to a NSArray, which will make the app crash.

Change the data variable in RestaurantViewController to a NSDictionary and change your prepareForSegue to pass a a NSDictionary

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
    if let cell = sender as? UITableViewCell {
        let i = redditListTableView.indexPathForCell(cell)!.row
        if segue.identifier == "toRestaurant" {
            let vc = segue.destinationViewController as RestaurantViewController
            vc.data = currentResponse[i] as NSDictionary
        }
    }
}  

For Swift 5

func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
        if let cell = sender as? UITableViewCell {
            let i = self.tableView.indexPath(for: cell)!.row
            if segue.identifier == "toRestaurant" {
                let vc = segue.destination as! RestaurantViewController
                vc.data = currentResponse[i] as NSDictionary
            }
        }
    }
Marshall
  • 141
  • 3
  • 9
ergoon
  • 1,264
  • 9
  • 17
  • I get an error on line 54: ``self.tableData = results as NSDictionary!``. The error is: ``'NSDictionary' is not convertible to 'NSArray'`` – Jack Feb 10 '15 at 12:00
  • in which viewcontroller? – ergoon Feb 10 '15 at 12:05
  • ``AutomaticCitySelectionViewController`` – Jack Feb 10 '15 at 12:05
  • no. don't change that line. you keep your tableData as a NSArray. But inside that Array you have NSDictionaries. So you need to change the `data` variable in RestaurantViewController to a NSDictionary – ergoon Feb 10 '15 at 12:07
  • So, on some lines down we have ``func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int { return tableData.count }`` My application breaks here, should I somehow try to nest out arrays and return them? – Jack Feb 10 '15 at 12:08
  • so in short. in `AutomaticCitySelectionViewController` the only thing you change is line 115 `vc.data = currentResponse[i] as NSDictionary` and in `RestaurantViewController` you only change line 13 `var data: NSDictionary!` – ergoon Feb 10 '15 at 12:11
  • I'm online now and awaiting for you in the chatroom – Jack Feb 10 '15 at 12:23
4

The following steps should fix your problem. If not, please let me know.

  1. Remove your tableView(tableView, didSelectRowAtIndexPath:) implementation.

  2. Make data on RestaurantViewController have type NSDictionary!

  3. Determine the selected row in prepareForSegue:

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
        if let cell = sender as? UITableViewCell {
            let i = tableView.indexPathForCell(cell)!.row
            if segue.identifier == "toRestaurant" {
                let vc = segue.destinationViewController as RestaurantViewController
                vc.data = currentResponse[i] as NSDictionary
            }
        }
    }
    
Tammo Freese
  • 10,514
  • 2
  • 35
  • 44
  • My console outputs ``(lldb)`` and *Thread 1* opens up (``swift_dynamicCastObjCClassUnconditional``). – Jack Feb 09 '15 at 14:21
  • Link to my [Xcode project](https://mega.co.nz/#!t89CiAKD!qOemhVZ6yCNDfsc-H43Yk4vady7yIqUx3V9d-4NrglQ) – Jack Feb 09 '15 at 14:25
  • Getting the error sounds like 90% of the way. If you replace the innermost two lines with `println(segue.destinationViewController!)`, what do you get as output? – Tammo Freese Feb 09 '15 at 14:29
  • ````is my output now – Jack Feb 09 '15 at 14:31
  • And the controller is pushed, only without the data, right? – Tammo Freese Feb 09 '15 at 14:45
  • If so, the problem should be the cast `currentResponse[i] as NSArray`. What's the type of `currentRepsonse`? – Tammo Freese Feb 09 '15 at 14:46
  • No, if I comment out ``prepareForSegue`` the controller shows correctly, but without data. With the ``prepareForSegue`` the app just crashes when I click a row. – Jack Feb 09 '15 at 14:47
  • ``currentResponse`` is a NSArray which gets populated with data, which on the other hand populates the table. – Jack Feb 09 '15 at 14:48
  • I could get it working by the changes described above. The main problem is that currentResponse is an array of dictionaries, so the type needs to be changed. In your code, `tableView` needs to be replaced with `redditListTableView`. – Tammo Freese Feb 09 '15 at 15:01
  • I replaced that of course, so the main problem is ``currentResponse``? How should I store the data then? Suggestion? – Jack Feb 09 '15 at 15:32
  • Storing it in a dictionary is totally fine! The problem was the cast `currentResponse[i] as NSArray` as `currentResponse[i]` is an instance of `NSDictionary`. Just change the type of `data` to `NSDictionary!` and the cast to … `as NSDictionary` and all is well :) – Tammo Freese Feb 09 '15 at 15:48
  • But then I guess I need to change some functions as well to get the JSON-parsing to work? .. – Jack Feb 09 '15 at 15:53
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/70581/discussion-between-tammo-freese-and-jack). – Tammo Freese Feb 09 '15 at 15:56
3

Dropbox link to stack3 directory

  1. I am having difficulty understanding why your software is much different than a standard 2 level tableview structure. So I coded a short example which you can access from this link. I have also included the sources code below.

  2. The program mimics what you have (as best as I understood it). Table Controller 1 segues to Table Controller 2 from the tableview cell. I had no issues with segue-ing. Notice that I do not have nor need to augment the Storybook to initiate the segue.

  3. I have embedded both the controllers in Navigation Controllers. My experience is that it saves a lot of effort to set up the navigation.

  4. Alternately, I could have control-dragged from the first TableViewController symbol on top of the screen to the second controller and set up the segue.

  5. I used a global variable (selectedRow) although it is not a recommend practice. But you just as easily use the prepareForSegue to set a variable in the RestaurantTableViewController (I show an example)

    1. Finally, I recommend checking the Connections Inspector (for the table view cell in the first controller) to confirm that there is a segue to the second controller. If you control-dragged properly there should be confirmation prompt as well as an entry in the Connections Inspector.

Unfortunately I just cant get the code properly formatter

import UIKit

var selectedRow = -1

class TableViewController: UITableViewController {

var firstArray = ["Item1","Item2","Item3","Item4"]

override func viewDidLoad() {

super.viewDidLoad()

}

override func didReceiveMemoryWarning() {

super.didReceiveMemoryWarning()

}

// MARK: - Table view data source

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {

return 1
}

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

return firstArray.count

}


let nameOfCell = "RestaurantCell"

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

let cell = tableView.dequeueReusableCellWithIdentifier(nameOfCell, forIndexPath: indexPath) as UITableViewCell

cell.textLabel!.text = firstArray[indexPath.row]

return cell

}

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

selectedRow = indexPath.row

}

// MARK: - Navigation

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {

let vc = segue.destinationViewController as RestaurantTableViewController

// can write to variables in RestaurantTableViewController if required

vc.someVariable = selectedRow

}


}


import UIKit

class RestaurantTableViewController: UITableViewController {

var secondArray = ["Item 2.1", "Item 2.2", "Item 2.3", "Item 2.4"]

var someVariable = -1

override func viewDidLoad() {

super.viewDidLoad()

}

override func didReceiveMemoryWarning() {

super.didReceiveMemoryWarning()

}

// MARK: - Table view data source

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {

return 1

}

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

return secondArray.count

}

let nameOfCell = "RestaurantCell"

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

let cell = tableView.dequeueReusableCellWithIdentifier(nameOfCell, forIndexPath: indexPath) as UITableViewCell

cell.textLabel!.text = secondArray[indexPath.row]

if indexPath.row == selectedRow {

cell.textLabel!.text = cell.textLabel!.text! + " SELECTED"

}

return cell

}

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

selectedRow = indexPath.row

}

}
Syed Tariq
  • 2,878
  • 3
  • 27
  • 37
  • This is how I set up my project too. Only that I'm confused about why your 2nd table view controller is pushed modally, instead of the default navigation controller way...and also why no back button appears. – PostCodeism Oct 06 '17 at 02:58
2

I noticed that in your screenshot of your storyboard, the segue is connecting the first prototype cell to the RestaurantViewController. This prototype cell looks like it's the "Basic" style of cell with a disclosure indicator accessory on the right. But look at the screenshot of your app running. The table is being populated with cells that appear to be the "Subtitle" style of cell without a disclosure indicator accessory on the right.

The reason that your segue is never firing no matter what you do is that the segue is only configured to work for a specific prototype cell, but that prototype cell is never being used when you populate the table. Whatever you're doing in tableView:cellForRowAtIndexPath:, you're not using the prototype cell that you want.

@Starscream has the right idea dequeueing the right cell with the right identifier and matching it with the identifier of the prototype cell in Interface Builder. The crash that you're getting even after doing that might be because of the previous problem mentioned in the comments above. Your segue in the storyboard is clearly pointing to a UITableViewController. Your code in prepareForSegue:sender: should be let vc = segue.destinationViewController as RestaurantViewController, as long as RestaurantViewController is a subclass of UITableViewController. You'll crash if you try to cast it as a UINavigationController. Also make sure that the class for the destination UITableViewController in the storyboard is listed as RestaurantController in the Identity Inspector pane. You'll crash if your program compiles thinking that the storyboard just contains a generic UITableViewController there.

Getting back to the original problem more, I don't know how you've implemented tableView:cellForRowAtIndexPath:, which might be crucial. Maybe it's not so simple. Maybe you plan on handling many prototype cells or generate custom cells at runtime. In this case, one way to make this simple for you is to programmatically perform the segue when the user taps on a cell. Instead of using a specific prototype cell, make the segue a connection originating from the "Restauranger nära mig" UITableViewController going to the RestaurantViewController. (Connect in Interface Builder by control-click dragging from the Table View Controller icon at the top of the first one over to the body of the second). You must give this segue an identifier in the Attributes Inspector pane to make this useful. Let's say it's "toRestaurant". Then at the end of your tableView:didSelectRowAtIndexPath: method, put this line of code: self.performSegueWithIdentifier("toRestaurant", sender: self). Now no matter what cell is selected in the table, this segue will always fire for you.

Christopher Whidden
  • 2,091
  • 1
  • 15
  • 16
1

Try creating cells like this in your cellForRow method:

let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier("MyTestCell", forIndexPath: indexPath)
Starscream
  • 1,128
  • 1
  • 9
  • 22
  • Now I got an error, ``Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'unable to dequeue a cell with identifier MyTestCell - must register a nib or a class for the identifier or connect a prototype cell in a storyboard'`` – Jack Feb 04 '15 at 15:39
  • The cell you've linked your segue to in IB should have a reuse identifier set as "MyTestCell" – Starscream Feb 04 '15 at 15:41
  • Added the ``MyTestCell`` to the TableViewCell identifier and got a my console wrote ``(lldb)``in green text and opened the thread. It says : ``EXC_REAKPOINT`` – Jack Feb 04 '15 at 15:43
1

Im going out on a whim here since I am just getting into swift right now but the way I do it in my prepareForSegue() is something like this:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
    if segue.identifier == "toRestaurant"{
        let navigationController = segue.destinationViewController as UINavigationController
        let vc = navigationController.topViewController as RestaurantViewController
        //notice I changed [i] to [index!.row]
        vc.data = currentResponse[index!.row] as NSArray
     }
    }

What it looks like to me is that you are calling the i variable which is kind of like a private variable inside a method of your class. You can do something like @Syed Tariq did with the selectRow variable and set it above your class SomeController: UIViewController /*, maybe some more here? */ { and then sign the variable inside your

override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

    selectedRow = indexPath.row

}

method like above but both ways should work rather well.

KyleMassacre
  • 362
  • 3
  • 14
  • I tried your ``prepareForSegue()`` but no luck, and that ``tableView`` method as well together with eachother. I just got the error ``0x102883662: nopw %cs:(%rax,%rax)``when clicking the table row. – Jack Feb 08 '15 at 16:54
  • Maybe it could be your ```currentResponse```? I don't know what that is exactly. Do you have that registered somewhere? – KyleMassacre Feb 08 '15 at 18:27
  • Don't think so, It's an empty ``NSArray``which gets populated in a function. – Jack Feb 08 '15 at 18:31
  • Is it something like this: ```class GamesTableViewController: UITableViewController { var currentResponse:NSArray? }``` Because as far as I know you need to declare it up at the top of the class in order to use it else where in the class. You can then set it within other areas like in: ```func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { currentResponse[indexPath.row] }``` Or in your ```prepareForSegue()``` method – KyleMassacre Feb 09 '15 at 01:24
  • Didn't work, just got an error in my thread ``0x10d81d662: nopw %cs:(%rax,%rax)`` – Jack Feb 09 '15 at 07:18
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/70561/discussion-between-kylemassacre-and-jack). – KyleMassacre Feb 09 '15 at 11:52
0

I had the same problem and I found the solution to be:

performSegueWithIdentifier("toViewDetails", sender: self)

func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    var cellnumber = procMgr.processos[indexPath.row].numero
    println("You selected cell #\(indexPath.row)")
    println(cellnumber)
    performSegueWithIdentifier("toViewDetails", sender: self)
}

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if segue.identifier == "toViewDetails" {
         let DestViewController : ViewDetails = segue.destinationViewController as! ViewDetails
    }
}
0

You may need to get the selected cell index of the UItableview. Below code used the selected cell index (UItableview.indexPathForSelectedRow) to get a correct element of the array.

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
    if segue.identifier == "seguaVisitCardDetial" {
        let viewController = segue.destinationViewController as! VCVisitCardDetial
        viewController.dataThisCard =   self.listOfVisitCards[(tblCardList.indexPathForSelectedRow?.row)!]
    }
}
Chamath Jeevan
  • 5,072
  • 1
  • 24
  • 27
0

I had this problem, too; the segue from UITableViewCell did not call. After some searching, I found it is because I had chosen "No Selection" for "Selection" field.