I have a simple test project. A UITableViewController (MasterViewController) embedded inside a navigation controller in storyboard. I am NOT segueing using the prepareForSegue to pass data to another view controller (DetailViewController). Instead, didSelectRowAtIndexPath is use to update a label in the detailviewcontroller as below:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
DetailViewController *detailViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"DetailViewController"];
NSMutableString *object = thisArray[indexPath.row];
detailViewController.passedData = object;
[self.navigationController pushViewController:detailViewController animated:YES];
}
Everything till this point works fine.
Now i have added another view controller in my storyboard. Made it the initial view controller, added two containers in it, then embedded both MasterViewController and DetailViewContainer in these containers.
Now instead of showing passed data inside the DetailViewController on the right side, its showing the passed data on the left side by replacing the controller view.
If i am not able to clarify what i am trying to say, here is the link to the project https://jumpshare.com/v/UiTFEB6AamIo8qX9sinW , its just for learning purpose.
Thanks
You're getting this problem because you are still doing this:
[self.navigationController pushViewController:detailViewController animated:YES];
The navigation controller you're referencing here is the one your master controller is embedded in, so you create an instance (different than the one that's already on screen) of detailController and push that onto the navigation controller.
What you want to do, is get a reference to the detail controller that's already on screen -- both child view controllers (the ones in the container views) are already instantiated when the app starts. So, you need to do this:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DetailViewController *detailViewController = [self.navigationController.parentViewController childViewControllers][1];
NSMutableString *object = thisArray[indexPath.row];
detailViewController.passedData = object;
}
This will pass the value to the detail controller, but you can't have the code to update the label in viewDidLoad, since that view is already loaded, and won't be called again. Instead, override the setter for passedData, and update the label there (note that I changed the name of the argument to passedInData, so it doesn't conflict with your property passedData):
-(void)setPassedData:(NSString *)passedInData {
passedData = passedInData;
detailDescriptionLabel.text = passedData;
}
Bu the way, unless you're planning on adding other controllers after your master view controller, there's no reason to have it embedded in a navigation controller at all, given this set up. If you take it out, then you need to remove the reference to self.navigationController when you get the reference to the detail controller. It would then just be:
DetailViewController *detailViewController = [self.parentViewController childViewControllers][1];
Related
Then I have my MasterViewController with its DetailViewController segued both.
I'm using Parse for backend platform, and I have a little problem for this: How to pass displaying datas of the MasterViewController to the DetailViewController
I'm using a NSArray called "RetrievingObjects" to retrieve it, For my Parse class querying it works successfully (I already used my query and my Array to display cells in MasterViewController), I use that method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DetailViewController *detailVc = [self.storyboard instantiateViewControllerWithIdentifier:#"detailVC"];
detailVC.lblDescription = [self.RetrievingObjects objectAtIndex:indexPath.row];
detailVC.lblTitleForDescr.text = [self.RetrievingObjects objectAtIndex:indexPath.row];
detailVC.dateForDescr.text = [self.RetrievingObjects objectAtIndex:indexPath.row];
[self performSegueWithIdentifier:#"goToDetailSg" sender:self];
}
It looks like you're not actually using detailVc anywhere. You should set it up in MasterViewController's prepareForSegue:sender: You can access what will become the DetailViewController through the segue object's destinationViewController property and set everything you need there.
Don't create the detail controller in code. If you have your segue set up correctly in the storyboard, that work will be done for you.
Instead, implement - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender, get the detail controller from the segue parameter, and pass the information you want it to display in that method.
Note that the destination controller's view will not have loaded at that point, so don't try to update IBOutlets.
In a split view controller app, how can I push multiple detail view controllers upon selecting a table row in the master view controller?
Just to be clear, I have splitviewcontroller with two different class:
1) MasterViewController - left handside View.
2) DetailViewController - right handside view
I need to add multiple ViewController above the DetailViewController using PushViewController (as a Stack), when I select a row in the master view controller. How do I wire up the view controllers? From the split view controller? or from the detail view navigation controller?
Assuming you want to replace the second view controller with another one, you can should be able to access the UISplitViewController via the parentViewController property of your master view controller and set a new viewControllers array. (See documentation at https://developer.apple.com/library/ios/documentation/uikit/reference/UISplitViewController_class/Reference/Reference.html)
- (void)tableView:(UITableView *)tv didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// ...
self.parentViewController.viewControllers = #[self, newViewController];
}
Assuming you are in your master view controller containing the table and it has a reference to the currently presented view controller which is a UINavigationController you can probably push the desired new view controller on the stack, no matter how often:
- (void)tableView:(UITableView *)tv didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
// ...
[self.detailViewController pushViewController:vc1 animated:YES];
[self.detailViewController pushViewController:vc2 animated:YES];
[self.detailViewController pushViewController:vc3 animated:YES];
}
This will add three view controllers to the view stack of the detailViewController. Adjust the variable names to your situation of course.
VC1 embeds VC2 in a container view. VC2 is a table VC.
Clicking a cell in VC2 pushes VC3.
VC3 embeds VC4 in a container view.
How do I get a reference to VC1 from within VC4.m?
I tried self.parentViewController.presentingViewController.parentViewController and self.parentViewController.presentingViewController but they didn't seem to work.
But then I decided to see if it would work when I used the delegate to store a reference to VC1, and still I was getting null for all of the public properties on VC1. Would VC1 not be in memory in this scenario? If not, why not? If so, why else would its (strong) properties be null?
edit: I've also just discovered through NSLogs that viewDidLoad in VC4 executes before didSelectRowAtIndexPath in VC2 finishes executing and setting the delegate reference, which may explain why that approach isn't working. How do I ensure the next VC is pushed only at the completion of all other lines in didSelectRowAtIndexPath? And regardless, there are other public properties on VC1 that are already set (not null) prior to a row being selected (I've just verified this with an NSLog in didSelectRow in VC2), and they are turning up null when I attempt to access them through VC4 through any approach.
(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
/*
<#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:#"<#Nib name#>" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
*/
// set reference to selected convo. it is stored in ConversationsParentVC public property
self.conversationsParentVC.selectedConvo = self.conversationsParentVC.conversationsArray[indexPath.row];
// temp
AppDelegate *delegate = getAppDelegate;
delegate.activeVC = self.conversationsParentVC;
ConversationsParentVC *convosParentVC = (ConversationsParentVC*)delegate.activeVC;
NSLog(#"boop %#", delegate.activeVC);
NSLog(#"beep %i", convosParentVC.conversationsArray.count);
}
When you set up a push segue from a table view cell, the segue fires before the tableView:didSelectRowAtIndexPath: method is called. You need to pass your information on to the destination view controller in the source view controller's prepareForSegue:sender: method.
Also, you might find this answer helpful.
You may also find it useful to know that the sender argument in prepareForSegue:sender: is the table view cell, and the table view's indexPathForSelectedRow has already been set to the cell's index path.
I am using Storyboard in my app and I want to pass data from one view to another view.
Instead of using segues I am using instantiateViewControllerWithIdentifier. In this case I am instantiate from my first TableViewController to a NavigationController which has a second TableViewController attached because I need the navigation in the second TableViewController. Now I want to pass data from my first TableviewController, depending which row was clicked, to my second TableviewController. In this case newTopViewController would be my NavigationController but my problem is now how to pass data from firstTableViewController to the secondTableviewController.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier = [NSString stringWithFormat:#"%#Top", [menuArray objectAtIndex:indexPath.row]];
UIViewController *newTopViewController = [self.storyboard instantiateViewControllerWithIdentifier:identifier];
}
If you instantiate a navigationController, you can use the viewControllers property to get the inner viewController of the navigation controller.
Something like this:
UINavigationController *navigationController = [self.storyboard instantiateViewControllerWithIdentifier:identifier];
MBFancyViewController *viewController = navigationController.viewControllers[0];
// setup "inner" view controller
viewController.foo = bar;
[self presentViewController:navigationController animated:YES completion:nil];
newTopViewController.anyVariableToShow= anyVariableToSend;
I do this pretty often on a few of my apps...
//Create new VC
CookViewController *detailViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"CookVC"];
//Set recipe
[detailViewController setRecipe:recipe];
//Pop over VC (can be pushed with a nav controller)
[self presentPopupViewController:detailViewController animationType:MJPopupViewAnimationFade];
If you aren't using a navigation controller or segues, then I think you need to reconsider your app design.
Actually it's not just a data pass problem as this is a program control and data transfer question together.
Even you would have to rethink about your app's concept, as you'd like to use storyboard without the meaning of storyboard, it's up to you and I hope you have good reason to do what you do.
So when you decided not to use segue you lost the new and comfortable way of instantiating a new controller and transferring data with it and you have to do the transfer of control and the data in two distinct steps. When you instantiate another scene in storyboard (like you do with instantiateViewControllerWithIdentifier:) you just instantiated a new controller and transferred the control but not the data. Just think about it as you instantiated a new controller from a xib in an old way (so you have to use initWithCoder: or awakeFromNib in the second view controller as the storyboard will not call initWithName:bundle:), but did not do anything more.
So you will have a new controller (it named in the identity part of the second storyboard) which is hanging in the universe without any relationship or connection with anything else (as the storyboard picture illustrates it nicely) and you could do with it what you'd like.
So you'd like to do something and you need data from the previous storyboard (ViewController). What you need is making available those data to the second storyboard(ViewController), and as you know there are lot of solution for this which were available long time before even storyboard is existed.
So regarding your code, the "data transfer" is depending on your design, whether the two controllers are subclasses of each other or whatsoever...
If you don't like to deal with subclassing and like to decoupling them as much as possible, the best way just make a property of your data in the first controller and refer to them from the second (after importing the first's .h file) and just refer to it in it's viewDidLoad or in initWithCoder: or anywhere where you need them, as
secondViewControllerdata = firstViewControllerdata.thatDataProperty
Of course you can do the same in reverse and make a property of the second controller and refer to it in your first view controller.
You can define some parameter in UIViewController to receive data:
#property (assign) int param1;
#property (retain) NSMutableArray *param2;
and use below to pass the data:
[newTopViewController setParam1:XX];
[newTopViewController setParam2:XX];
I have a UITableViewController within a UIViewController. While this table viewcontroller was the only one involved, it was pushing views just fine when the user would tap a row. However, ever since I moved it to be one of two contained within the UIViewController, the taps of rows suddenly do nothing.
I've tried searching around and I'm not the first to run into this problem, but none of the answers fit my circumstances or the questions have no working answers. That link was the closest I found, but I'm not using storyboards -- I'm using separate XIBs.
So how do I push a new view from a viewcontroller within a viewcontroller?
To recap:
Here is what I had, and it worked fine in taking users to a new screen!
// Normal table behavior, as illustrated by [another question][2].
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
SomeView *detailViewController = [[SomeView alloc] initWithNibName:#"SomeView" bundle:nil];
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
}
Now I have the viewcontroller as a property in a view -- and the above code, which is in the file for the tableviewcontroller and not at the "main" view, doesn't cause a new screen to appear anymore!
Thanks for the comments! Here's some code to clarify my scenario.
The controllers within a controller. This is a file from a test project I've been using to test the concept out. In this case, I have a tableview controller within a tableview controller.
#interface SimpleTableViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
// This is the controller within the controller
#property IBOutlet SecondTableViewController *secondTableController;
#property IBOutlet UITableView *secondTable;
My SecondTableViewController has this fun bit.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
UIViewController *detailViewController = [[UIViewController alloc] initWithNibName:#"SimpleNonTableViewController" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[manualViewControllerParent.navigationController pushViewController:detailViewController animated:YES];
}
The view that the user interacts with is hooked up to SimpleTableViewController. In this way, SecondTableViewController is "within" SimpleTableViewController. Feel free to comment if you'd like more details!
I've put my test/concept project on github. https://github.com/hyliandanny/TableViewCeption
You need to use a custom container controller to do what you want. It would be easiest if you used a storyboard, but you can do it in code with xibs as well. The outer controller should be a UIViewController, not a table view controller. You can do something like this (in the outer controller):
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
UIViewController *detailViewController = [[UIViewController alloc] initWithNibName:#"SimpleNonTableViewController" bundle:nil];
[self addChildViewController:detailViewController];
detailViewController.view.frame = set the frame to what you want;
[self.view addSubview:detailViewController.view];
[detailViewController didMoveToParentViewController:self];
}
You should read up on Apple's documentation for custom container controllers.
What you need to make sure:
Your UITableView delegate is hooked up to your controller. Otherwise it wouldn't call didSelectRow. You can do this in xib or in viewDidLoad method.
Your self.navigationController is not nil
Your detailViewController is not nil
I also think that what you mean is you have UITableView inside your UIViewController. UITableView is only the view, whereas UITableViewController is a controller. You can't have a controller inside another controller.