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.
Related
I have added a segue between two view controllers using XCode 6 interface builder. However it calls the default constructor. How can I get it to call a specific constructor for the second view?
view 1 -> click button -> activates segue -> calls constructor of view 2 -> displays view 2
Using a segue this is unfortunately not possible, you can't further specify how the destinationViewController should be instantiated.
However, instead of using the Storyboard segue, you can instantiate the UIViewController yourself and just push it manually onto the navigation stack.
- (IBAction)buttonTap
{
ViewController2 *vc2 = <your custom constructor>;
[self.navigationController pushViewController:vc2 animated:YES];
}
(note that this mimics the exact same behaviour that the push segue gives you)
Otherwise, if you want to keep on using the Storyboard segue and your actual goal is to initialize certain properties of ViewController2, you can implemented prepareForSegue: and set the properties there.
- (void)prepareForSegue:(UIStoryboardSegue *)segue
{
ViewController2 *vc2 = segue.destinationViewController;
// set properties here
vc2.prop = xyz;
}
(note the code is not tested but it should convey the main ideas, let me know if you need further explanation)
I have a couple of "ViewControllers" and one for Update a picture in an iOS app.
The first one has a button when tapped asks if the user wants to use gallery photo or camera.
Now i am presenting this controller by using presentViewController on self.
But when the second view controller is presented i want to set the UIImagePicker source according to what the user has passed in.
I have made 2 different methods. one with camera source and one with "photoslibrary".
I don't know how to invoke one of these methods based on the use choice from the previous controller.
Am i going the right way with this approach? or should i just have one controller?
Basically there are two ways of passing data to a view controller.
Storyboard
If you are working with storyboard segues (that is, control drag from your "root" view controller to the destination view controller, choose a transition style and define a identifier), you can present the view controller
via
[self performSegueWithIdentifier:#"yourSegueIdentifier" sender:self];
Most of the times your destination view controller will be a custom class, so define a public property to hold the data you want to pass through. Then implement the following in your "root" view controller
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// Setup the location menu delegate
if([segue.identifier isEqualToString:#"yourSegueIdentifier"]) {
// The custom class of your destination view controller,
// don't forget to import the corresponding header
ViewControllerCustomClass *vc = segue.destinationViewController;
// Set custom property
vc.chosenImageId = self.chosenImageId;
// Send message
[vc message];
}
}
Hints:If your destination view controller is the root view controller of a navigationViewController you can access it via [[segue.destinationViewController childViewControllers] objectAtIndex:0]; Additionally, as senderis an id, you can "abuse" it to pass any object through, just as a NSDictionary, for example.Also note, that when I am referring to the root view controller, I am talking of the view controller from which we segue to the destination from.
Programmatically
ViewControllerCustomClass *vc = [[ViewControllerCustomClass alloc] init];
vc.chosenImageId = self.chosenImageId;
// If you want to push it to the navigation controller
[self.navigationController pushViewController:vc animated:YES];
// If you want to open it modally
[self presentViewController:vc animated:YES completion:nil];
You can use inheritance, make your previous controller superClass, and invoke method in presentViewController in viewDidload.
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];
While trying out storyboards for one of my projects I came across something for which I don't have a good solution;
I have a navigation-based application which shows a UITableViewController. The tableView is populated with user-created elements. Tapping an element cell brings up the detail view controller. The user can create a new element by tapping a button in the tableView. This brings up a modal view, which will handle the creation.
Now, when a user is done with creating the element and dismisses the modal view controller, I want the user to see the corresponding new detail view controller and not the tableview. But I can't figure out how to achieve this in storyboards.
Does anyone have a good pattern for this?
Current situation
TableView --(tap create)--> creation modal view --(finish creating)--> TableView
Should be
TableView --(tap create)--> creation modal view --(finish creating)--> detail view
You can put the creating view controller in a navigation controller and link the creation view controller to the detail view controller as well with a push segue. When you finish creating the data it will direct to an instance of detail view controller.
If you want to navigate back from details view directly to the table view, you can add a property to the details view controller, say #property (nonatomic) BOOL cameFromCreationViewController;. You can set this property in prepareForSegue: in the source view controller. In the details view make your own back button, and when it's tapped, you can do this:
if(self.cameFromCreationViewController){
[self.presentingViewController dismissViewController];
}
else {
[self.navigationController popViewController]
}
The best pattern I could come up with, is the same as the old pattern in code.
Add a property (nonatomic, weak) UINavigationController *sourceNavigationController to the modal view controller. When it becomes time to dismiss the modal view controller, add the following code:
DetailViewController *detailViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"DetailViewController"];
detailViewController.element = newlyCreatedElement;
[[self sourceNavigationController] pushViewController:detailViewController animated:NO];
And to make sure the sourceNavigationController get set properly, add the following code in the prepareForSegue: of the TableView:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"newElementSegue"]) {
[[segue destinationViewController] setSourceNavigationController:self.navigationController];
}
}
This is the first time I'm trying to implement navigation from a tableView cell to another tableView using UINavigationController and it doesn't work for me.
I'm NOT using nib file and I have a simple tableView that I present it in a modal dialog in my app, it works fine, now I added the disclosureInidcator to one of it's cell, to make the user enable to choose from a fixed number of options available from another list(tableView). For this purpose I have another class that makes the second tableView. the problem is now navigation from the cell(contains disclosure icon)in first tableview to second tableView doesn't do anything, no error, no nothing. I guess the way I setup the navigation controller would be wrong, the code doesn't fall in delegate, or datasource of the second class at all.
in First TableView in method : didSelectRowAtIndexPath I tried to catch that row, then call the second tableView like this:
mySecondViewController *secondVC = [[[mySecondViewController alloc] initWithStyle:UITableViewStyleGrouped ] autorelease];
UINavigationController *navCont = [[UINavigationController alloc] initWithRootViewController: self];//not sure the first controller should act as the root controller?
[navCont pushViewController:secondVC animated:YES]; //it does nothing, no error,...
the second tableViewcontroller class contains all delegate and datasource methods, and initialization method:
- (id)initWithStyle:(UITableViewStyle)style
{
if ((self = [super initWithStyle:style])) {
}
return self;
}
and declared in interface as:
#interface stockOptionViewController : UITableViewController {
}
I tried to play with viewDidLoad, but didn't help.
Please help me cause I have no clue and all sample codes found is based on using nib files.
Thank,
Kam
Your navigation controller should be the root view controller of the app delegate's window, and the first view controller should be the root view controller of the navigation controller, then you can push new controllers onto it.
Please see the documentation for UINavigationController.
At the moment, you are creating a navigation controller but not putting it anywhere, so asking it to push new view controllers is a little pointless. You have the right code, just not in the right order.
You can present view control modally without nav controller
mySecondViewController *secondVC = [[[mySecondViewController alloc] initWithStyle:UITableViewStyleGrouped ] autorelease];
[self presentModalViewController:secondVC animated:YES];
UINavigationController should be the root view controller. In the current code, navCont is not on the view stack, so it won't work. Instead of pushing myFirstViewController in your appDelegate, push the UINavigationController to the stack and add myFirstViewController as its root view controller.