18

I'm quite new to xcode, and I am trying to develop a sample app that is basically an embedded tableview which has many levels. I have a plist which stores the cells of each tableview. If the cells do not have children, then I want to be able to go to one detailed view once the cell is pressed. Eventually I want to be able to go to different detailed views depending on data type. To do this, I created a detailed view from storyboard, dragged my view controller to my detailed view to create a manual "Push" segue, and labeled the segue "segue1".

Edit: Source code here

Building Manual Segue

Next I populate what I assumed to be the necessary functions for this to work, which is to call [self performSegueWithIdentifier:@"segue1" sender:myString]; where myString is the title of the cell I selected.

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    //Check the dictionary to see what cell was clicked
    NSDictionary *dict = [self.tableDataSource objectAtIndex:indexPath.row];
    NSString *myString = [dict objectForKey:@"Title"];
    NSDictionary *dictionary = [self.tableDataSource objectAtIndex:indexPath.row];

    NSArray *children = [dictionary objectForKey:@"Children"];

    //If there is no children, go to the detailed view
    if([children count] == 0)
    {
        [self performSegueWithIdentifier:@"segue1" sender:myString];

    }
    else{
        //Prepare to tableview.
        DrillDownViewController *rvController = [[DrillDownViewController alloc] initWithNibName:nil bundle:[NSBundle mainBundle]];

        //Increment the Current View
        rvController.CurrentLevel += 1;

        //Set the title;
        rvController.CurrentTitle = [dictionary objectForKey:@"Title"];

        //Push the new table view on the stack
        [self.navigationController pushViewController:rvController animated:YES];

        rvController.tableDataSource = children;

    }

}

Finally, I called prepare for segue which looks for the segue labeled segue1.

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if([[segue identifier] isEqualToString:@"segue1"])
    {
        DrillDownDetailController *dvController = [[segue destinationViewController] visibleViewController];
        //DrillDownDetailController *dvController = [[DrillDownDetailController alloc] initWithNibName:nil bundle:[NSBundle mainBundle]];
        [dvController setItemName:(NSString *)sender];
        [self.navigationController pushViewController:dvController animated:YES];
    }
}

I thought this would work, but for some reason, whenever the code reaches [self performSegueWithIdentifier:@"segue1" sender:myString];, it breaks with the error

***** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver () has no segue with identifier 'segue1'' * First throw call stack: (0x14b4022 0xeb4cd6 0xdf61b 0x3590 0xa85c5 0xa87fa 0x93d85d 0x1488936 0x14883d7 0x13eb790 0x13ead84 0x13eac9b 0x139d7d8 0x139d88a 0x17626 0x23ed 0x2355 0x1) terminate called throwing an exception(lldb)

I can't figure out why it's telling me it can't find segue1 when it's already defined in storyboard and the code.

rene
  • 41,474
  • 78
  • 114
  • 152
foboi1122
  • 1,727
  • 4
  • 19
  • 36
  • 1
    I may be COMPLETELY OFF here as I don't use segues much but try declaring `DrillDownDetailController *dvController` as an instance variable then in `prepareForSegue` set whatever properties you need to on the VC. Don't push the VC to naviation controller in prepare and then performsegue. – mkral Sep 20 '12 at 18:22
  • @mkral could you elaborate on "Don't push the VC to naviation controller in prepare and then performsegue"? So remove the pushViewController line and put that line in a new performSegue function? – foboi1122 Sep 20 '12 at 18:28
  • I'll add an answer that's formatted. If it doesn't work hopefully someone else can come along – mkral Sep 20 '12 at 18:32
  • 1
    I've updated my answer to address two key issues that I saw. 1. segue didn't have an identifier; 2. you're calling `initWithNibName` to create a view controller to push to, but with storyboards you should use `instantiateViewControllerWithIdentifier`. – Rob Sep 20 '12 at 21:25

2 Answers2

23

A couple of problems, actually:

First, in that project you uploaded for us, the segue does not bear the "segue1" identifier:

no identifier

You should fill in that identifier if you haven't already.

Second, as you're pushing from table view to table view, you're calling initWithNibName to create a view controller. You really want to use instantiateViewControllerWithIdentifier.

Thus, the line that says:

DrillDownViewController *rvController = [[DrillDownViewController alloc] initWithNibName:nil bundle:[NSBundle mainBundle]];

should say:

DrillDownViewController *rvController = [self.storyboard instantiateViewControllerWithIdentifier:@"TableView"];

Third, your prepareForSegue was:

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if([[segue identifier] isEqualToString:@"segue1"])
    {
        dvController = [[segue destinationViewController] visibleViewController];
        [dvController setItemName:self->nameToSend];
    }
}

And it should be simplified to eliminate reference to visibleViewController, e.g.:

-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
    if([[segue identifier] isEqualToString:@"segue1"])
    {
        dvController = segue.destinationViewController;
        dvController.itemName = nameToSend;
    }
}

Fourth, your DrillDownDetailController.m has this method:

-(void) setItemName:(NSString *)itemName
{
    if(!itemName)
    {
        itemName = [[NSString alloc] initWithString:itemName];
    }
    label.text = itemName;
}

There are a bunch of problems here, but you simply should not be updating the label.text here (because it might not yet be created!). You should just eliminate this custom setter method completely (just let Xcode synthesize the standard setter for you) and just change your viewDidLoad to read as follows:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    label.text = self.itemName;
}

Make sure you don't update UI objects until viewDidLoad!

I haven't gone through the whole program, but with those four corrections I can tap on "SubItem1", and then "Cars", and then "BMW" and I see a detail screen that says "BMW". I think there are some problems with your plist on other items (e.g. the shoes entries are strings, and not dictionary entries and you get an error ... I presume you just haven't filled in your plist completely), but the above fixes correct the more significant coding issues.

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
2

THE MAIN ISSUE IS HERE, you were creating a blank DrillDownViewController, not reusing the one from storyboards, see comments in code below

    -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
    {
        if([[segue identifier] isEqualToString:@"segue1"])
        {
             [segue.destinationViewController setItemName:(NSString*)sender];
        }
    }

    - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
    {
        //Check the dictionary to see what cell was clicked
       NSDictionary *dict = [self.tableDataSource objectAtIndex:indexPath.row];
NSString *titleName = [dict objectForKey:@"Title"];
NSDictionary *dictionary = [self.tableDataSource objectAtIndex:indexPath.row];

NSArray *children = [dictionary objectForKey:@"Children"];

if([children count] == 0)
{
    [self performSegueWithIdentifier:@"segue1" sender:titleName];

}
        else{
    //Prepare to tableview.

    //THE MAIN ISSUE IS HERE, you were creating a blank DrillDownViewController, not reusing the one from storyboards so it did not have a segue at all defined. instead do this:
    UIStoryboard*  sb = [UIStoryboard storyboardWithName:@"MainStoryboard"
                                                  bundle:nil];
    DrillDownViewController *rvController = [sb instantiateViewControllerWithIdentifier:@"DDVC"];       
    //Increment the Current View
    rvController.CurrentLevel += 1;

    //Set the title;
    rvController.CurrentTitle = [dictionary objectForKey:@"Title"];

    //Push the new table view on the stack
    [self.navigationController pushViewController:rvController animated:YES];

    rvController.tableDataSource = children;

}


    }
mkral
  • 4,065
  • 4
  • 28
  • 53
  • can you send NSString through the sender? Or is that frowned upon. most of the time i see sender:self but I didn't see any reference to the sender in the code so I assumed that I could send whatever I want as the sender – foboi1122 Sep 20 '12 at 18:44
  • I'm actually not sure about that, but I don't think you should, instead you should send the cell that was clicked if anything then in prepareForSegue get the string. – mkral Sep 20 '12 at 18:47
  • because in this case the button/cell clicked IS the sender. – mkral Sep 20 '12 at 18:48
  • Looks like that didn't work, but I put my source code in the original post if anyone would like to take a look at it. Thanks! – foboi1122 Sep 20 '12 at 19:01
  • Ill take a look at the source – mkral Sep 20 '12 at 19:23
  • Ok, I got it working but there were a ton of things I had to fix. It may be easier for you to just look at the source and ask questions if you have any. – mkral Sep 20 '12 at 19:59
  • Nvm, I corrected answer to reflex the MAIN issue. You weren't using the storyboard viewcontroller nib so it had NO SEGUE – mkral Sep 20 '12 at 20:04
  • Also notice that I set the VC's identifiers to DDVC and DDDC in your storyboard – mkral Sep 20 '12 at 20:07
  • Thanks so much! I didn't know you have to manually link your storyboard in the code! A few things I'm confused about... In the storyboard xml.. I notice that you don't have the portion for the drilldownDetailController. How/Why did you remove that portion? And what is the difference between declaring a variable inside the interface vs. declaring it as a property? Did you redo the segue between the views as well? It seems like my segue was a little different from yours. – foboi1122 Sep 20 '12 at 21:47
  • Hmm, not sure about the thing, anything I removed in there was unintentional I only used the storyboard editor, not the XML directly. Declaring it as a property will allow you to set that instance's variable without defining get and set methods. I may have added some unneeded one, the only important one is textName in the DDDC view controller. I deleted the segue, created a new one to check and also set its identifier (in your source you didn't as far as I could tell but your OP said you did). – mkral Sep 20 '12 at 21:53
  • Oh man... thanks so much! you've save me a lot of headache and taught me some things I didn't know about xcode. I really appreciate your help. It all works great now! – foboi1122 Sep 20 '12 at 22:19
  • 1
    Awesome, go ahead and mark @Rob 's answer as the correct one. His clearly describes each problem which may be useful for the next guy/gal with this issue. – mkral Sep 20 '12 at 22:30