14

Below is the code that I am currently running. I have a storyboard setup with a nav controller and tableview controller and a view controller. I am trying to pass the name from the NSDictionary that I have setup for the Table to the detail view controller. Should I use prepareforsegue or didselectrowatindexpath and how would I get the name out of the dictionary to pass it along?

#import "FMInboxViewController.h"
#import "FMDetailViewController.h"

@interface FMInboxViewController ()

@end

@implementation FMInboxViewController

@synthesize keyArray;
@synthesize tableArray;
@synthesize tblDictionary;
@synthesize filteredArray;

- (void)viewDidLoad
{
    [super viewDidLoad];

    NSMutableArray *ary=[[NSMutableArray alloc]init];
    [ary addObject:@"Adam"];
    [ary addObject:@"Fred"];
    [ary addObject:@"Angel"];
    // ... many similar entries
    [ary addObject:@"James"];
    [ary addObject:@"Mukthesh"];

    self.tblDictionary =[self fillingDictionary:ary];
}

Table View Data Source

#pragma mark - Table view data source

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    // Return the number of sections.
    return [keyArray count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // Return the number of rows in the section.
    NSArray *ary = [self.tblDictionary valueForKey:[keyArray objectAtIndex:section]];
    return [ary count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];

    NSString *key = [keyArray objectAtIndex:[indexPath section]];
    NSArray *array = (NSArray *)[self.tblDictionary valueForKey:key];
    NSString *cellTitle = [array objectAtIndex:[indexPath row]];
    cell.textLabel.text = cellTitle;

    // Configure the cell...

    return cell;
}

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    NSString *title = [keyArray objectAtIndex:section];
    return title;
}

//-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//    NSString *key = [keyArray objectAtIndex:[indexPath section]];
//    NSArray *array = (NSArray *)[self.tblDictionary valueForKey:key];
//    self.selectedName = [array objectAtIndex:indexPath.row];
//    NSLog(@"Selected Name in Did select: %@", self.selectedName);
//    
//    [self performSegueWithIdentifier:@"showDetail" sender:self];
//}

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"showDetail"]) {
        NSIndexPath *section = [self.tableView indexPathForSelectedRow];
        NSString *key = [keyArray objectAtIndex:section];
        NSArray *array = (NSArray *)[self.tblDictionary valueForKey:key];
        NSString *cellTitle = [array objectAtIndex:[indexPath row]];
        NSLog(@"Selected Name in Did select: %@", self.selectedName);
    }
}

Helper methods

#pragma mark - Helper Methods

- (NSMutableDictionary *)fillingDictionary:(NSMutableArray *)sentArray {
    keyArray = [[NSMutableArray alloc] init];
    [keyArray removeAllObjects];
    NSMutableDictionary *dic = [[NSMutableDictionary alloc] init];
    [sentArray sortUsingSelector:@selector(compare:)];
    for ( NSString *str in sentArray) {
        NSString *charVal = [str substringToIndex:1];
        NSString *charStr = [NSString stringWithString:charVal];
        NSLog(@" charStr = %@", charStr);
        if (![keyArray containsObject:charStr]) {
            NSMutableArray *charArray = [[NSMutableArray alloc] init];
            [charArray addObject:str];
            [keyArray addObject:charStr];
            [dic setValue:charArray forKey:charStr];
        }
        else {
            NSMutableArray *prevArray = (NSMutableArray *)[dic valueForKey:charStr];
            [prevArray addObject:str];
            [dic setValue:prevArray forKeyPath:charStr];
        }
    }

    return dic;
}


@end

OK, I changed that section to look like this

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
    NSString *key = [keyArray objectAtIndex:[indexPath section]];
    NSArray *array = (NSArray *)[self.tblDictionary valueForKey:key];
    self.selectedName = [array objectAtIndex:indexPath.row];
    NSLog(@"Selected Name in Did select: %@", self.selectedName);
}

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    FMDetailViewController *dvc = (FMDetailViewController *)segue.destinationViewController;
    dvc.name = self.selectedName;
}

However now when I select row the name won't show up in the detail controller on the first press. If you go back and select another name the first name that you pressed then shows up in the view controller. Any suggestions on why this occurs?

James Webster
  • 31,873
  • 11
  • 70
  • 114
fbm351
  • 225
  • 2
  • 3
  • 7
  • In `prepareForSegue:` did you probably mean: `NSString *key = [keyArray objectAtIndex:section.row];` ? (`index.row` instead of `index`) – CouchDeveloper Nov 19 '13 at 15:24
  • I don't see where in your `didSelectRowAtIndexPath` you called `[self performSegueWithIdentifier:asdf sender:self];` – Peter Foti Nov 19 '13 at 16:14
  • I broke up your code a bit into sections since most of it was hidden behind scroll bars. I also remove some of the redundant data. You should aim to be as concise as possible with your questions. – James Webster Nov 19 '13 at 16:51

4 Answers4

33

You need to use both, in didSelectRowAtIndexPath you should call [self performSegueWithIdentifier:@"identifier" sender:self];

In the same View Controller you should have the prepareForSegue method grab the destinationViewController out of the segue, and then set whatever properties on that view controller that you wish to set.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    self.someProperty = [self.someArray objectAtIndex:indexPath.row];
    [self performSegueWithIdentifier:@"segueID" sender:self];
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    UIViewController *vcToPushTo = segue.destinationViewController;
    vcToPushTo.propertyToSet = self.someProperty;
}
Peter Foti
  • 5,526
  • 6
  • 34
  • 47
  • And why would he need to use both? He can just push it with in the didSelectRow if you're already instantiating a ViewController in prepareForSegue. – Lord Zsolt Nov 19 '13 at 15:12
  • 2
    Performing a segue and preparing for one are two different things. – Peter Foti Nov 19 '13 at 15:17
  • I understand, but there's no point in doing a segue (unless you want to change the animation) when you can just use navigationController's pushViewController method. – Lord Zsolt Nov 19 '13 at 15:18
  • Actually, you don't need code at all. In a table view, you can trigger a segue with the "selection" or the "accessory action". – CouchDeveloper Nov 19 '13 at 15:18
  • @CouchDeveloper yes, there are many ways to trigger a segue, but if you want to pass along data the best time to do it is in `prepareForSegue`. – Peter Foti Nov 19 '13 at 15:33
  • Yes, of course. But you can declare a *trigger* in IB - you don't need to implement `didSelectRowAtIndexPath:` and `performSegueWithIdentifier:`. – CouchDeveloper Nov 19 '13 at 15:36
  • Triggering the segue from the accessory button (in the cell) within IB has been added in some version of Xcode 4.x. I don't remember when exactly. The ability to trigger a segue from the table view selection was possible since the invention of storyboards. The reason to NOT use IB to trigger segues is probably old habits, or backwards compatibility, or simply old code that got updated to storyboards. – CouchDeveloper Nov 19 '13 at 15:45
  • What if their is no accessory button? Regardless, its something I would like to see fleshed out if you'd care to submit the answer or go to chat. I've never seen it done before, nor have I seen anyone mention it. – Peter Foti Nov 19 '13 at 15:48
  • OK I added the above code changes. However now when I select row the name won't show up in the detail controller on the first press. If you go back and select another name the first name that you pressed then shows up in the view controller. Any suggestions on why this occurs? – fbm351 Nov 19 '13 at 15:55
  • Where do you set the property in the second view controller? – Peter Foti Nov 19 '13 at 15:58
  • I set the property in viewDidLoad. - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. NSLog(@"Name: %@", self.name); self.nameLabel.text = self.name; } – fbm351 Nov 19 '13 at 16:05
  • - (void) viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; NSLog(@"Name in detail view: %@", self.name); self.nameLabel.text = self.name; } 2013-11-19 11:08:51.678 TableSections[64678:70b] Selected Name in Did select: Adam 2013-11-19 11:08:51.680 TableSections[64678:70b] Name in detail view: (null) – fbm351 Nov 19 '13 at 16:11
  • @PeterFoti added a answer which illustrates how to add a segue in IB. ;) – CouchDeveloper Nov 19 '13 at 16:35
  • 1
    "frankly I don't see why they would" -- for simplicity? I do it the way @CouchDeveloper said all the time. If you connect the segue from the cell, you don't need to implement didSelectRowAtIndexPath or call performSegueWithIdentifier. In prepareForSegue, you use the sender argument (which will be the cell) to get the indexPath of the selected cell for passing the data to the destination view controller. – rdelmar Nov 19 '13 at 17:39
  • 2
    This is a great approach when you are using a Xib for the cell, and cannot directly connect the segue to a cell. It's better than doing just pushViewController because it allows you to have the representation in the storyboard, to see what is happening visually. – Ken Ko Mar 13 '15 at 02:33
  • I like this answer the most. I'd like to add that the sender XXX in [self performSegueWithIdentifier:@"segueID" sender:XXX]; can be anything. You can put NSIndexPath here too. basically any object. Then you can use it in prepareForSegue. – GeneCode Sep 08 '16 at 08:14
28

If possible (and this is the case in almost all standard scenarios), I would use Interface Builder to trigger the segue. You still need to implement prepareForSegue: to configure the destination view controller.

In order to create a segue in IB which triggers a detail view when tapping on a cell or an accessory button (on the right most side of the cell) perform these steps:

  1. Control drag from Table View Cell to the destination view controller and release the mouse or trackpad. This opens small selection panel.

  2. Choose the source of the trigger, either "Selection segue" or "Accessory action" and the type of the segue ("push", "modal" or "custom").

  3. In the Attributes Inspector pane define the "Identifier" of the segue, e.g. "UserShowSegue".

Here's an image from a Demo storyboard in IB which illustrates how the "Table View Cell" in the "Users" view controller is setup to trigger a "push" segue to a detail view controller:

enter image description here

jlehr
  • 15,557
  • 5
  • 43
  • 45
CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
  • 2
    This seems to be the most modern approach and could use more upvotes to beat the current selected answer. – Jonny Sep 21 '14 at 03:57
  • but in this answer you are not saying how to pass data from the selected row to the destination controller – sports May 19 '15 at 02:51
  • @sports You can retrieve data from the `sender` parameter in `prepareForSegue:segue:sender:`. The `sender` is the object that initiated the segue. You might use this parameter to perform different actions based on which control (or other object) initiated the segue respectively based on the data you retrieve from that object. – CouchDeveloper May 19 '15 at 10:04
  • And how can I make the sender to be the tapped row? Is it automatic if the sender goes from the prototype cell to the new ViewController? – sports May 19 '15 at 17:08
  • 1
    @sports If I'm not way off the mark, the `sender` is the actual `UITableViewCell` which contains the accessory view which has been tabbed. When you have the cell, there are a few ways to get the index path. See also http://stackoverflow.com/questions/13690008/how-to-find-indexpath-for-tapped-accessory-button-in-tableview – CouchDeveloper May 19 '15 at 17:52
  • @sports And from the `segue` argument you can obtain the destination and source view controller, see https://developer.apple.com/library/prerelease/ios/documentation/UIKit/Reference/UIStoryboardSegue_Class/index.html – CouchDeveloper May 19 '15 at 17:58
2

Might be an old question, but to be more detailed for whom it may concern:

1- Use both as '(@Peter-Foti)' mentioned.

2- Segue should be linked from the ParentVC to the DestinationVC (NOT from the Prototype Cell).

3- Sender should be set properly.

4- set your '@property (someProperty)'.

SAMPLE CODE:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    self.someProperty = [self.someArray objectAtIndex:indexPath.row];
    [self performSegueWithIdentifier:@"segueID" sender:self.someProperty];
}

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
     UIViewController *vcToPushTo = segue.destinationViewController;
     vcToPushTo.propertyToSet = sender;
}
P.J.Radadiya
  • 1,493
  • 1
  • 12
  • 21
iTRQ
  • 29
  • 1
2

Because didSelectRowAtIndexPath can be replaced with storyboard visual programming, I recommend to do all the logic in the prepareForSegue like this:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    let cell = sender as! UITableViewCell
    let indexPath = tableView.indexPath(for: cell)!
    ... and then use indexPath to set up the destination
}
William Entriken
  • 37,208
  • 23
  • 149
  • 195