101

I'm using storyboards and I have a UITableView. I have a segue setup that pushes from my table to the detail VC. But which method should I use to handle this? I'll have to pass a couple objects to the detail view. But do I use didSelectRowAtIndex or -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender?

TheNeil
  • 3,321
  • 2
  • 27
  • 52
Jon
  • 4,732
  • 6
  • 44
  • 67

6 Answers6

199

If you use prepareForSegue:sender:then you won't have as much to change if you later decide to trigger the segue from some control outside the table view.

The prepareForSegue:sender: message is sent to the current view controller, so I'd suggest something like this:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    // Assume self.view is the table view
    NSIndexPath *path = [self.tableView indexPathForSelectedRow];
    DetailObject *detail = [self detailForIndexPath:path];
    [segue.destinationViewController setDetail:detail];
}

In Swift:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    let path = self.tableView.indexPathForSelectedRow()!
    segue.destinationViewController.detail = self.detailForIndexPath(path)
}
Zack Shapiro
  • 6,648
  • 17
  • 83
  • 151
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • 1
    Ok, can you give an example of how to implement it with passing an object based on indexPath. – Jon Nov 15 '11 at 03:04
  • 4
    shouldn't `self.view` be just be `sender` here? I couldn't even get `[self.view indexPathForSelectedRow]` to work, I had to do `[sender indexPathForSelectedRow];` – ladookie Dec 23 '11 at 17:26
  • How would you do this in Swift? – User Sep 26 '14 at 18:00
  • @robmayoff Thanks for updating this for Swift. I made some slight edits to reflect recent language changes. Hope this helps some others – Zack Shapiro Feb 12 '15 at 23:21
  • It is weird that `tableView.indexPathForSelectedRow()` contains the correct value when `prepareFroSegue...` is called: `tableView(_:didSelectrowAtIndexPath:)` isn't called until later. – Nicolas Miari Jun 07 '16 at 02:21
5

I did this and it worked

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

    NSLog(@"Row Selected = %i",indexPath.row);

    [self performSegueWithIdentifier:@"testID" sender:self.view];    
}
Rohit Mandiwal
  • 10,258
  • 5
  • 70
  • 83
  • 11
    That doesn't make sense. You should either use the segue OR the table view delegate. Just create a segue from the Cell and it will automatically perform the same thing you did without writing code. – Yariv Nissim Jan 23 '13 at 08:44
  • 3
    how do you assign a segue to a cell without didSelectRow? – Morkrom Nov 27 '13 at 17:15
3

When sender is UITableViewCell, you may ask UITableView to query indexPath of the cell.

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if let cell = sender as? UITableViewCell {
            let indexPath = self.tableView.indexPathForCell(cell)!
            assert(segue.destinationViewController.isKindOfClass(DetailViewController))
            let detailViewController = segue.destinationViewController as! DetailViewController
            detailViewController.item = self.items[indexPath.row] // like this
        }
    }
Kaz Yoshikawa
  • 1,577
  • 1
  • 18
  • 26
1

if your tableView property is in another class and you only have one section, then you could use the tag property to store the cell's row like:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

     UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];

     cell.tag = indexPath.row;

     return cell;
}

And then you can access it as the sender is the same cell with the row value in its tag:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {

    MyDestinationViewController *destinationViewController = segue.destinationViewController;
    destinationViewController.myProperty = [tableViewElementsArray objectAtIndex:[sender tag]]; // sender will be your cell 
}
Laszlo
  • 2,803
  • 2
  • 28
  • 33
1

self.tableView.indexPathForSelectedRow returns selected cell, but not segue sender cell, e.g. sender cell is not selected (accessory action), or in multiple selection case. The best way is getting indexPath for segue sender:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    __auto_type itemViewController = (id<ItemViewController>)segue.destinationViewController;
    itemViewController.senderIndexPath = [self.tableView indexPathForCell:sender];
}

In Swift:

protocol ItemViewController {
    var senderIndexPath : IndexPath? { get set }
    var selectedIndexPaths : [IndexPath]? { get set }
}

override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
    if  let cell = sender as? UITableViewCell,
        var itemViewController = segue.destination as? ItemViewController {
        itemViewController.senderIndexPath = tableView.indexPath(for: cell)
        itemViewController.selectedIndexPaths = tableView.indexPathsForSelectedRows
    }
}
Stanislav P.
  • 145
  • 1
  • 7
0

If you use prepareForSegue: you can check who is the sender and execute different code

For example in swift

override func prepareForSegue(segue: UiStoryboardSegue, sender: AnyObject?)
{
   var senderIsTableviewCell:Bool! = sender?.isKindOfClass(UITableViewCell)

   if senderIsTableviewCell
   {
       //do something
   }
}
Gabriel
  • 27
  • 6
Eugene
  • 151
  • 8
  • 2
    Just do: *if let tableViewCell = sender as? UITableViewCell { // do something }*. If sender can't be cast to a UITableViewCell, "do something" won't execute. – mbeaty Mar 04 '15 at 03:23