next view controller is not loaded when the segue is performed - iphone

I am still new to the storyboard stuff. I spent two days debugging this problem. It seems the destination view controller of a segue is not working properly when the -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender is called.
I have a table view and when a cell is tapped, it calls for a segue
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
VAItem *item = [category itemAtIndex:indexPath.row];
[self performSegueWithIdentifier:#"SegueShowDetail" sender:item];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"SegueShowDetail"]) {
VADetailViewController *detailViewController = segue.destinationViewController;
detailViewController.item = (VAItem*) sender; // <-- THIS ASSIGNMENT DOES NOT WORK
}
}
My debugging shows, the item is correctly passed down to prepareForSegue but the detailViewController.item is a wild pointer. It's not initialized and the = assignment does not have any effect.
I put a breakpoint in VADetailViewController's viewDidLoad and found out that the item variable is still the wild pointer address even though the assignment has taken place.
#interface VADetailViewController : UIViewController
// data
#property (nonatomic, retain) VAItem *item;
#end
item is also correctly synthesized.
Any help is much appreciated
Leo

If you're using a storyboard segue to transition to the next viewcontroller you don't need to use tableVeiew:didSelectRowAtIndexPath: in this situation. Depending how you have things set up that could be contributing to the problem.
In your storyboard, make sure you have drawn your push segue from your tableview's Prototype Cell to the detail view controller (i.e. from the cell, NOT from the tableViewController). If you have your storyboard set up that way your segue will be triggered as soon as the user selects a cell. A reference to the cell itself will be passed to prepareForSegue:sender: as the sender parameter. You can then inspect that object to find out which row was clicked in that method.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"SegueShowDetail"]) {
// sender will be the tableview cell that was selected
UITableViewCell *cell = (UITableViewCell*)sender;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
VAItem *item = [category itemAtIndex:indexPath.row];
VADetailViewController *detailViewController = segue.destinationViewController;
detailViewController.item = item;
}
}

You really only want to use one of
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
to pass your data along to the next view controller. If you want to use the latter in your case, then you need to change your code to something along the lines of:
if ([segue.identifier isEqualToString:#"SegueShowDetail"]) {
VADetailViewController *detailViewController = segue.destinationViewController;
detailViewController.item = [catagory itemAtIndex:[self.tableView indexPathForSelectedRow].row];
}
I think you were getting an error because of what you assigned your sender. Here is the reference to the docs that explain what 'sender' should be.
https://developer.apple.com/library/IOs/#documentation/UIKit/Reference/UIViewController_Class/Reference/Reference.html#//apple_ref/occ/instm/UIViewController/performSegueWithIdentifier:sender:
Hope this helps.

Related

UITableView prepareForSegue assigning indexPath to sender?

In the code below I am just trying out setting up a segue directly from the UIViewController to a DetailViewController. I usually do the segue from the UITableViewCell (which I think is clearer) but this time I just wanted to try going directly from the ViewController using -tableView:didSelectRowAtIndexPath:
My question is: I got to a situation where I needed to access the UITableViewCell indexPath from within -prepareForSegue:sender: my solution was to send the indexPath via sender. Usually I would set sender to self here, I have looked around and I can't find any reference to this, I am just curious if using sender to pass an indexPath is acceptable? I am pretty sure it is, and I am sure I have read that you can somewhere before, but I can't find any reference to it now.
// ------------------------------------------------------------------- **
// DELEGATE: UITableView
// ------------------------------------------------------------------- **
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"%s", __PRETTY_FUNCTION__);
[self performSegueWithIdentifier:#"SEGUE_TWO" sender:indexPath];
}
// ------------------------------------------------------------------- **
// SEGUE: ViewController_ONE > DetailViewController
// ------------------------------------------------------------------- **
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
NSLog(#"%s", __PRETTY_FUNCTION__);
if([[segue identifier] isEqualToString:#"SEGUE_TWO"]) {
NSUInteger cellRow = [(NSIndexPath *)sender row];
DetailViewController *destinationController = [segue destinationViewController];
[destinationController setDataString:[[self dataList] objectAtIndex:cellRow]];
}
}
EDIT: What I was looking for in this case was
NSIndexPath *indexPath = [[self tableView] indexPathForSelectedRow];
essentially a way to get indexPath into -performForSegue:sender: without having to pass it via the sender parameter (in -performSegueWithIdentifier:sender:)
I typically avoid using the sender in prepareForSegue: because it could be anything. This makes asserting that your sender is what you are expecting more cumbersome and fragile. Also, I prefer to check for the class of the destinationViewController object rather than the segue name. Your functionality is tied to your object (or more specficially its interface), not the title of your segue, so checking your class makes it clear what you are trying to accomplish.
Below is the usual template I use in production when segueing from a UITableView. Note that you should always check if the destinationViewController is embedded in a nav controller and then check the class of the object as well to make sure you are handling it correctly. Finally, use an assert to ensure that somewhere down the line you haven't added some new segue that you forgot to handle.
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// unwrap the controller if it's embedded in the nav controller.
UIViewController *controller;
UIViewController *destVC = segue.destinationViewController;
if ([destVC isKindOfClass:[UINavigationController class]])
{
UINavigationController *navController = (UINavigationController *)destVC;
controller = [navController.viewControllers firstObject];
}
else
{
controller = destVC;
}
if ([controller isKindOfClass:[DetailViewController class]])
{
DetailViewController *vc = (DetailViewController *)controller;
NSIndexPath *ip = [self.tableView indexPathForSelectedRow];
[vc setDataString:[[self dataList] objectAtIndex:ip.row]];
}
else
{
NSAssert(NO, #"Unknown segue. All segues must be handled.");
}
}
You could use UITableView's method indexPathForCell. I hope you have a tableView instance variable.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([sender isKindOfClass:[UITableViewCell class]]) {
NSIndexPath * indexPath = [self.tableView indexPathForCell:sender];
//Your code here
}
}

Issue passing string using segue

I have one table view controller and another view controller. I want to pass data from selected cell (from table view) to view controller and update a label in view controller. I'm doing this by following way:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"SubRecipeConnectorSegue"])
{
RecipeDetailViewController *destViewController = segue.destinationViewController;
destViewController.recipeName = selectedRecipe;
}
}
The problem is, when, for the 1st time I'm selecting a cell it not appearing in the label; but, when I'm selecting any 2nd value; my 1st selected value is displaying in label.
Similarly when selecting 3rd value, 2nd value is getting showed in label.
Can any one please tell me what is going wrong? Please help.
The way to do this is to trigger the segue manually. In IB, draw the segue from one VC to the other (not from the table cell). Make sure to give it an identifier. In tableView:didSelect, set the selected string and call performSegueWithIdentifier:.
The problem you're seeing is the segue prep gets called before the tableView delegate, so the new VC is presenting the prior selection, which is undefined the first time.
You should get the data from the corresponding cell in the didSelectRowAtIndexPath and then perform the segue grammatically.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *recipeString = [dataArray objectAtIndex:indexPath.row];
[self performSegueWithIdentifier:#"SubRecipeConnectorSegue" sender:recipeString];
}
Then change your prepareForSegue method into
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(NSString*)recipeString
{
if ([segue.identifier isEqualToString:#"SubRecipeConnectorSegue"])
{
RecipeDetailViewController *destViewController = segue.destinationViewController;
destViewController.recipeName = recipeString;
}
}
I don't know how you have stored the data in the cells, so that might differ from the example I wrote. If you need help implementing please let me know.

xcode ios: prepareForSegue - same data 2 display. 2nd display data error

I'm in the process of learning to use the storyboard to create tables.
When user Tabs on the cell, it goes to View A, while if user Tabs on the arrow icon on the right inside the cell it goes to View B.
Both A & B are to display the same data, just the layout is different. I'm using UIViewController for A and UITableViewController for B. Its just having different layout but same data.
The problem I have is that, view A shows the correct data for each cell, but view B always shows the data of cell[0] no matter which cell I Tab on.
The prepareForSegue code is as follow:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
`if ([[segue identifier] isEqualToString:#"ShowCarDetails"])
{
CarDetailViewController *detailViewController =
[segue destinationViewController];
NSIndexPath *myIndexPath = [self.tableView
indexPathForSelectedRow];
int row = [myIndexPath row];
detailViewController.carDetailModel = #[_carMakes[row],
_carModels[row], _carImages[row]];
}
else if ([[segue identifier] isEqualToString:#"niceDetails"])
{
StaticTableViewController *detailViewController =
[segue destinationViewController];
NSIndexPath *myIndexPath = [self.tableView
indexPathForSelectedRow];
int row = [myIndexPath row];
detailViewController.carDetailModel = #[_carMakes[row],
_carModels[row], _carImages[row]];
}
}
Can someone point out where's the error here?? Thanks!
And otherwise check the data in the StaticTableViewController.
Are you synchronising the data etc?

How do I change a UILabel's text on a pushed (with segue) view, relative to the senders selected accessory?

I have a UITableView that pushes, via a storyboard segue, a view, which displays a UILabel that I wish to change the text on relative to the indexPath.row of the selected accessory on the UITableView.
I know it's probably wildly wrong, but this was my attempt. I feel like I'm going about it very wrong:
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
[self performSegueWithIdentifier:#"ArticlePreviewSegue" sender:self];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSIndexPath *indexPath = [sender indexPathForSelectedRow];
ArticlePreviewViewController *apvc = [segue destinationViewController];
NSDictionary *article = [_newsFetcher.articles objectAtIndex:indexPath.row];
apvc.titleLabel.text = [article objectForKey:#"title"];
apvc.bodyLabel.text = [article objectForKey:#"body"];
}
Thanks you!
One problem may be that tapping the accessory doesn't select the row. You can handle that by passing the index path as the sender of the segue:
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
[self performSegueWithIdentifier:#"ArticlePreviewSegue" sender:indexPath];
}
Now you can access the index path in prepareForSegue:sender: without relying on the row being selected.
Another problem is that in prepareForSegue:sender:, apvc hasn't loaded its view yet. So apvc.titleLabel and apvc.bodyLabel are both nil.
The proper way to handle this is to give ArticlePreviewViewController an article property and set that property in prepareForSegue:sender:, like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSIndexPath *indexPath = (NSIndexPath *)sender;
ArticlePreviewViewController *apvc = [segue destinationViewController];
apvc.article = [_newsFetcher.articles objectAtIndex:indexPath.row];
}
Then, in -[ArticlePreviewViewController viewDidLoad], you can set the labels based on the article:
- (void)viewDidLoad {
[super viewDidLoad];
self.titleLabel.text = self.article[#"title"];
self.bodyLabel.text = self.article[#"body"];
}

Different behaviour of prepareForSegue and tableView: didSelectRowAtIndexPath:

I am testing a simple scenario that has a UITableViewController that has the cell segue to another UIViewController. Now during that segue, I need to pass some information to the UIViewController such as name and price of an item. I wanted to test the difference between using prepareForSegue and didSelectRowAtIndexPath: to pass those 2 data points. It seems to work with the didSelectRowAtIndexPath: but not prepareForSegue. After doing some debugging:
1-I am wondering if prepareForSegue happens before any IBOutlet on the destinationViewController are initialized and so you can't effectively set them. So prepareForSegue seems to be more geared towards passing non-IBOutlet properties.
2-If I am using a regular push segue, how can I access the "segue.destinationViewController" from the didSelectRowAtIndexPath: method.
PS: When testing the prepareForSegue, I am setting the segue in IB to push and when testing the didSelectRowAtIndexPath:, I am setting the segue in IB to modal.
PS: All resemblances to real names are incidental!
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"itemDetailSegue"])
{
NSIndexPath *indexPath=[self.tableView indexPathForSelectedRow];
NSString *theItemName=[[self.itemsForSale objectAtIndex:indexPath.row]objectForKey:#"Name"];
NSString *theItemPrice=[[self.itemsForSale objectAtIndex:indexPath.row]objectForKey:#"Price"];
JuanDetailVC* juanDetailVC= (JuanDetailVC*) segue.destinationViewController;
juanDetailVC.itemName.text=theItemName;
juanDetailVC.itemPrice.text=theItemPrice;
}
}
-(void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *theItemName=[[self.itemsForSale objectAtIndex:indexPath.row]objectForKey:#"Name"];
NSString *theItemPrice=[[self.itemsForSale objectAtIndex:indexPath.row]objectForKey:#"Price"];
JuanDetailVC* juanDetailVC= (JuanDetailVC*) self.presentedViewController;
juanDetailVC.itemName.text=theItemName;
juanDetailVC.itemPrice.text=theItemPrice;
}
Thanks
You're right in that you cannot set outlet properties in prepareForSegue. You'll have to pass the properties into another property, then in viewDidLoad of the destination view controller set the outlet's property. For example: you can't set the text of a label, you'll have to create a second property (e.g., an NSString *labelText) and in viewDidLoad set the label's text (self.label.text = self.labelText).
You don't have access to the destination view controller in tableView:didSelectRowAtIndexPath. You'll have to create the instance yourself by alloc/initing it.