I have a view controller. When I press a button in it, a popover controller with a uitableview shows up. I select a row, which shows another view with some controls in it. When I press a button that says "Save Item", I want it to dismiss the popover. How do I do this?
Here's what I've tried:
Using the delegate and protocol pattern. This hasn't worked since in order to push another view inside my tableview, the whole thing must be embedded in a navigation view controller, so when I segue, it segues to a nav controller, not the tableview which I could set the popover delegate for.
Adding my main view as a member of the view I want to dismiss from. I don't know why this doesn't work.
The Hard Clean Way
There are four view controllers in the story, plus a popover controller. I will call the three view controllers "main view controller", "nav", "vcA", and "vcB". As I understand it, "nav" is the initial content view controller of the popover and has "vcA" as its root view controller.
main view controller -> popover controller -> nav -> vcA -> (later) vcB
When you present the popover from your main view controller, you keep a reference to the popover controller. This is what makes dismissing possible, as you know.
When you create the Save button, you make its target the main view controller and its action a method in the main view controller. You will have to set this up in code; it cannot be configured from a storyboard because you cannot form an action from one scene to another. (You are able to do this because you started out with a reference to nav and vcA when you configured the popover controller initially. Thus you can hand vcA a reference to self, the main view controller. If necessary, you can then pass this reference down the chain from vcA to vcB as vcB is summoned and pushed onto the navigation stack.)
Now the user taps Save, your main view controller's method runs, and it uses its reference to the popover controller to tell it to dismiss.
The Easy Dirty Way
The heck with all that. The main view controller registers for an NSNotification. The Save button posts that NSNotification. Done. :)
The Middle Way
You could set the whole chain up in your storyboard using a popover segue, and do the dismissal through an Unwind segue matched by an unwind method back in the main view controller. I never think of this initially, because I don't like popover segues very much. But it does work.
This is how I solved my problem (sorry for the bad english):
First, Create a property of UIStoryboardPopoverSegue in the VcA and set it from the main view controller.
Nav -> VcA_ViewController
#property (strong, nonatomic) UIStoryboardPopoverSegue *popupSegue;
Then, in the Main View Controller prepareForSegue set the property:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
if([segue.identifier isEqualToString:#"your segue from the mainview to the navigation"]) {
UINavigationController *navigationController = (UINavigationController *)c;
VcA_ViewController *vcA = (VRPointOfInterestsFiltersViewController *) navigationController.topViewController;
vcA.popupSegue = (UIStoryboardPopoverSegue*)segue;
} }
Now, from the VcA controller you can have the dismiss button
- (IBAction)dismissPopoup:(id)sender {
[self.popupSegue.popoverController dismissPopoverAnimated:YES]; }
Don't forget to link the popOverSegue from the MainViewController to the NavController.
Hope it helps!
Related
I have two View Controllers: LevelSelectViewController and GameViewController.
Neither are the root view controller for the app I am making (the root view controller is called MainViewController).
How can I use the navigation method pushViewController:animated: for this transition for LevelSelectViewController to GameViewController?
In my LevelSelectViewController, you click a button and the following action method performs:
- (IBAction)buttonPressed:(id)sender {
// the pushViewController:animated: method hopefully can be used
// other code
}
How can I use the navigation method pushViewController:animated: for this transition for LevelSelectViewController to GameViewController?
The term "root view controller" can be a little confusing because UIWindow has a rootViewController property, and UINavigationController has a initWithRootViewController parameter. If you're calling -pushViewController:animated:, you must have a navigation controller in your view controller graph since that method belongs to UINavigationController. If your LevelSelectViewController instance is part of a navigation stack, you can do this:
- (IBAction)buttonPressed:(id)sender {
// create a game controller
GameViewController *gameController = [[GameViewController alloc] initWithNibName:nil bundle:nil];
// push it
[self.navigationController pushViewController:gameController animated:YES];
// other code
}
A more typical thing to do these days, though, is to put all the view controllers in a storyboard and simply connect the button to a push transition.
If you're using storyboards just create a segue. If you're not passing any information you can push to it by control dragging from one view controller to another. It doesn't matter if it's the root view controller or not.
If you want to pass data create a segue on storyboards from the view controller, not the index path or button etc. and invoke the prepareForSegueMethod in the view controller with the exact same identifier as the segue on storyboards.
I have a tab bar controller with 3 view controllers and one navigation view controller in it.
That navigation view controller has a table view controller and detail view.
In my first view controller (first tab) there are three buttons. I want the first button to load the detail view in the navigation controller. (as seen on image below)
Storyboard
How can i achieve this ?
I have tried calling the method that performs the segue in LocationsTableViewController, but that gives me the error "Receiver () has no segue with identifier 'addLocation'". Although a segue "addLocation" certainly exists.
Method connected to button in StartViewController:
- (IBAction)addLocationView:(id)sender {
LocationsTableViewController *LTVC = [[LocationsTableViewController alloc] init];
[LTVC addLocation];
}
LocationsTableViewController:
-(void)addLocation
{
[self performSegueWithIdentifier: #"addLocation" sender: self];
}
Your "addLocation" segue is from the Locations Table View Controller to the Add Location View Controller, so the segue exists on the LocationsTableViewController. That is correct. However, if your current view controller isn't LocationsTableViewController, it doesn't make sense to call that segue.
You have a few options:
Storyboard Option #1: Make a new segue from StartViewController to AddLocationViewController, and perform that segue in addLocationView:.
Storyboard Option #2: Make a new segue from the button to the AddLocationViewController, and remove your IBAction. The storyboard will automatically call that segue when the button is clicked.
Programatic Option: Instantiate a new AddLocationViewController and push it onto the navigation stack: [self.navigationController pushViewController:[[AddLocationViewController alloc] init] animated:YES].
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];
}
}
I simply want to add a navigation bar (with some nav bar button) on a presented modal controller with storyboard.
Programmatically with XIBs, it looks like that :
SDMapController *mapController = [[SDMapController alloc] initWithNibName:#"SDMapController" bundle:nil];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:mapController];
[self presentModalViewController:navigationController animated:YES];
But I have no idea how to handle it with Storyboard. I guess i have to implement some code on the -(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender method but since the destinationController property of segue object is readonly, i don't really know how to do this.
Any idea ?
You have to implement the prepare for segue, only if you want to pass on some data to your presented view controller. Otherwise you can leave it empty. The presenting of the View Controller is from the Interface Builder. You add a navigation controller with it's root view controller and make a segue (ctrl + drag) to the navigation controller. Set the segue type to modal, and give it an ID. You can trigger this segue from code by calling [self perforSegueWithIdentiefier:#"MySegueID"];. If you dragged the segue from a button or a table view cell, it will be triggered automatically when you tap on it, without calling this method. As I said, in the prepareForSegue method, the segue.destinationViewController will bee the presented navigation controller. You can access it's topViewController if you need and pass some data to it.
I'm trying to use a popover as an intermediary menu between my main view and a modal view controller. I can successfully present the Modal view controller from the popover by using the following code:
UIStoryboard *storyboardiPad = [UIStoryboard storyboardWithName:#"MainStoryboard_iPad" bundle:nil];
cbwEditControlPanel *editCP = [storyboardiPad instantiateViewControllerWithIdentifier:#"EditCP"];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:editCP];
[nav setToolbarHidden:NO];
[nav setModalPresentationStyle:UIModalPresentationFullScreen];
[nav setModalTransitionStyle:UIModalTransitionStyleCoverVertical];
[self presentViewController:nav animated:YES completion:nil];
self.modalInPopover = NO;
The problem I'm running into is that when the EditCP modal view controller is dismissed, the main view controller never updates. I have a pagecontroller on the main view that should be updated to reflect the number of pages as set in the EditCP modal view controller, but for some reason the modal view controller being called from the popover prevents the main view controller from updating the pagecontroller. I've even tried calling the main view's "View Will Appear" method from the popover or modal view when they are dismissed, but even if the 'viewWillAppear' method is called the pageController will not update!
Any ideas what is preventing the pageController from updating? I even passed a reference to the pagecontroller to the modal view and tried to update it there, but it seems that from the time the popover is presented until it is dismissed, I cannot update the number of pages on the PageController.
Thank you!
So this is an old question but I also came across a similar problem recently when using a popover. My solution was to use an unwind segue to trigger my parent view to perform some action. In my case my parent view contains contact information and the popover contains a list of cites. All I wanted to do was to have the parent view update with the new city once the user selected it from the popover. So in my parent view I create my unwind function as follows:
In the .h:
- (IBAction)unwindToContactTVC:(UIStoryboardSegue *)unwindSegue;
In the .m:
- (IBAction)unwindToContactTVC:(UIStoryboardSegue *)unwindSegue
{
[self updateTableForOffice];
}
In the above .m file is where you would have the logic to do whatever it is you want to in the parent view. To connect this unwind segue go to the child view in the storyboard and control drag from the view icon to the exit icon. You should see a pop up with the name of your unwind segue.
Finally, give that unwind segue a name and then in the child controller in the viewWillDisappear() function call the segue as follows:
- (void)viewWillDisappear:(BOOL)animated
{
[self performSegueWithIdentifier:#"unwind-to-contact-tvc" sender:self];
}
I hope that helps. If someone has a better solution let me know.
Well, I half solved the problem. The only way to get an update function when the popover disappeared was to stop using Storyboards and programmatically present the popover, using the main view as the delegate. I then was able to update correctly inside the popoverControllerDidDismissPopover method.
However, I am still interested in finding a way to update the pageControl when the modal is dismissed, before the popover is dismissed.