Different behaviour of prepareForSegue and tableView: didSelectRowAtIndexPath: - ios5

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.

Related

Xcode/Obj-c - Segue pushes new instance

I have seen similar questions a lot on Stackoverflow and I tried a lot of things but I can't seem to figure this out. I have multiple TableViewControllers and 1 MainViewController. The MainViewController has buttons calling the different TableViewControllers and on selecting a tablecell the tableViewController dismisses.
The problem is that im pushing a new instance of my MainViewController every time I push from either one of my tableViewControllers. I currently use Segues to push between these different controllers.
In short: When switching from TableViewControllers to ViewController I want to prevent the ViewController to get pushed as a new instance because this way its removing my previous data input.
Im pretty sure I have to use either:
[self dismissModalViewController: withCompletion:]
performSegue
prepareForSegue
Or set some global variables in a class and call those, but im not experienced enough yet to implement this correctly.
A simple example of end result would be: 3 textfields in VC. On clicking textfield1 it opens tableview1 and on clicking a cell it updates textfield1. Textfield2 opens tableview2, etc.
Hope im clear enough, could post sample code if needed.
Edit, posting code (keep in mind, segues are performed in storyboard):
TableViewExample.h:
#interface IndustryViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
NSArray *tableViewArray;}
#property (nonatomic, strong) IBOutlet UITableView *tableViewIndustry;
TableViewExample.m:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"showIndustry"]) {
NSIndexPath *indexPath = [self.tableViewIndustry indexPathForSelectedRow];
ViewController *destViewController = segue.destinationViewController;
destViewController.industryText = [tableViewArray objectAtIndex:indexPath.row];
destViewController.industryTextName = [tableViewArray objectAtIndex:indexPath.row];
}}
Then in ViewController.m, viewDidLoad:
[industry setTitle:industryText forState:UIControlStateNormal];
These are the most important parts I think.
Is the segue of type "Push"? If so you should try dismissing the table view controllers using:
[self.navigationController popViewControllerAnimated:YES];
If the segue is of type "Modal" instead you should do something like this on your table view controller:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// your logic here
[self dismissModalViewControllerAnimated:YES];
}
As for the data exchange between controllers what I would personally do is creating a public property in the header file of the Table View Controller, like the following:
#property (nonatomic, weak) <Your_UIViewController_Subclass_Here> *mainController
Than, in the main controller, override the prepareForSegue:sender: method to set the newly created property to point to the main controller, like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
<Your_Subclass_Of_UITableViewController_Here> *destinationController = segue.destinationController;
destinationController.mainController = self;
}
Now the Table View Controller will have a pointer to the main controller to send the data basically all you have to do is to implement some public method or property in the Main Controller to be called when the user selects a table view row in the table view controller in order to update the text in the textfields or whatever data model you are using.

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.

tableView not updating in iphone

I am developing an app which is similar to the settings app in iphone.
In my first VC i have a 4 rows upon selection it takes to the second VC. my second VC displays list of items. Once the user selected, the selected text should be displayed on the firstVC adjacent to the one selected.
How to achieve it with the help of dictionary objects or in other way.!Thanks in advance...
I did a similar task a few days back and here is how I accomplished that. Note, there might be many alternatives to achieving what you want.
KeyPoint: UITableView is dependent upon its DataSource (array or dictionary) for its data to be displayed in cell as well as number of sections and rows per section.
Now, initially your DataSource has values that are displayed as default. Upon tapping the row, initialize and push the 2ndViewController on navigation stack. In this 2ndViewController, you must somehow update the 1stViewController's DataSource (replacing original values).
Approach 1
You can use Protocols & Delegates
Create a protocol as following.
#protocol MyTableDelegate
- (void) dismissWithData:(NSString*)data;
Create a delegate reference in 2ndViewController, Call that delegate method and pass the selected data
#interface 2ndViewController : UIViewController
#property (assign) id<MyTableDelegate> delegate;
#property (assign) NSIndexPath *ip;
// Also synthesize it
#implementation 2ndViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[self.delegate dismissWithData:#"Second Table Selection"];
[self.navigationController popViewControllerAnimated:YES];
}
Now in 1stViewController, implement the TableView & Protocol Method
#interface 1stViewController : UIViewController <MyTableDelegate>
#implementation 1stViewController
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
2ndViewController *controller = [[2ndViewController alloc] initWithNib....];
// Set the Delegate to Self
controller.delegate = self;
self.ip = indexPath;
// Push Controller on to Navigation Stack
}
- (void) dismissWithData: (NSString*) data
{
// We Will Store NSIndexPath ip in didSelectRowAtIndex method
// Use the ip to get the Appropriate index of DataSource Array
// And replace it with incoming data // Reload TableView
[self.dataSource replaceObjectAtIndex:[ip row] withObject:data];
[self.tableView reloadData];
}
Approach 2
You could also pass the data source to 2ndViewController in the didSelectRowAtIndexPath method..
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
2ndViewController *controller = [[2ndViewController alloc] initWithNib....];
controller.dataArray = self.dataSource;
// Push Controller on to Stack
}
Then in the same didSelectRowAtIndexPath method of 2ndViewController, update the data source, and call
[self.navigationController popViewControllerAnimated:YES];
also, you need to reload your tableview in 1stController's ViewWillAppear method
- (void)ViewWillAppear:(BOOL)animated
{
[self.tableView reloadData];
[super ViewWillAppear:animated];
}
I think you need to call [self.tableView reloadData] in viewDidAppear. If possible do go through this link Reload UITableView when navigating back?
The array that you are using in your first VC. Pass that array to the second VC, update it there in the secondVC class and write this statement "[firstVC.tableView reloadData]" in the viewWillAppear method of your firstVC class.
This will update your array of firstVC class in secondVC class when user selects anything in secondVC class, and than when user naivgates back table view of firstVC is reloaded. So that the update array is reflected in table view.

UIButton within a UITableViewCell to Perform a Segue to Another UIViewController Using Storyboard

In my App I have a ParentViewController that have several elements in it. One of the elements is a UITableView.
I designed the table's cells using a Dynamic Prototype Cell that I had designed within the Storyboard.
Meaning, that I have in the Storyboard a UIViewController; inside it I have a UITableView; and inside the UITableView I have a Prototype Cell.
Now, I added a UIButton inside the the Prototype Cell, created another UIViewController (let's call it ChildViewController) and connected the UIButton and the ChildViewController within the Storyboard with a Segue.
How should I write the code that activates the Segue when the user clicks the UIButton?
I managed to create the table itself and have data inside the Dynamic Cells (they have some UILabels aside to the UIButton), but I cannot figure out how to connect the UIButton to the code properly.
I don't want to use the UITableView default "select" features, since I wish to have several buttons inside the Dynamic Cells - each one will derive a different segue to a different ChildViewController.
Here's how I solved the same problem. Hope it will help.
To do this, you can pass the pointer to your UITableViewController to your custom cell's initialization method, and store it there:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// ...
CDTrackCell *cell = [tableView dequeueReusableCellWithIdentifier:strCellTableIdentifier];
CDTrack *currTrack = [self.fetchedResultsController objectAtIndexPath:indexPath];
[cell configureView:currTrack inTableViewController:self];
return cell;
}
Save the pointer in custom UITableViewCell class:
- (void) configureView:(CDTrack*)track
inTableViewController:(CDTracksViewController*)_tvc
{
self.tvc = _tvc;
// ...
}
Then, you can perform a segue when the button is tapped:
- (IBAction)buttonAddToTracklistTapped:(UIButton *)sender {
[self.tvc performSegueWithIdentifier:#"showAddToTracklist" sender:self];
}
Any better ideas?

next view controller is not loaded when the segue is performed

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.