I'm sorry if this duplicates other threads. I've pored through about a dozen, over several hours, but none seem to quite apply to my situation. Namely;
A button presents a popover
The popover contains a table view, nested inside a navigation controller
The user navigates to the second level of the nav controller (a second tableViewController), then makes a selection
Upon making the selection, the popover should dismiss, and pass back the indexPath.row to the original screen.
Importantly, I'm using storyboards and segues to do this (this may be part of the problem!)
I've tried implementing custom delegate methods to do this, but I'm getting hopelessly tangled up. Mainly because
a) The actual delegate is two levels away, and I'm having trouble conveying this "up the chain", as it were.
b) The [segue destinationViewController] is the navigationController. I'm not sure how to get a hook into the actual tableViews it contains, to retrieve or set properties (such as the delegate)
Does this make any sense to anyone? Reading back, this question is almost as bamboozled as I am. If you can decipher it, and have any advice, I'd be very grateful.
You can get to the actual view controller (which has your table view) using the viewControllers property of your navigation controller (segue.destinationViewController). Once you have a pointer to this view controller, set its delegate. Then in tableView:didSelectRowAtIndexPath, notify the delegate that something was selected, and the delegate can dismiss the popover.
EDIT: This could be in your prepareForSegue:
UINavigationController *navigationController = (UINavigationController *)segue.destinationViewController; // cast the destination to UINavigationController
SpeciesTableViewController *speciesViewController = [navigationController.viewControllers lastObject];
speciesViewController.delegate = self;
Apple docs about the viewControllers property of a UINavigationController:
The view controllers currently on the navigation stack. . . . The root
view controller is at index 0 in the array, the back view controller
is at index n-2, and the top controller is at index n-1, where n is
the number of items in the array.
When using a segue, the root view controller is the only view controller, so lastObject always returns the root view controller.
Now, keep in mind that when you select a species in SpeciesTableViewController, you're triggering a segue, and will have to set the delegate of SpeciesDetailViewController. In SpeciesDetailViewController's didSelectRowForIndexPath you can send a message to the delegate to dismiss the popover.
Related
I have a UIViewController sublcass (VC1) embedded in a UINavigationController. VC1 triggers a modal segue to another UIViewController subclass (VC2) which is embedded in its own, different UINavigationController. Inside of an action method triggered by a UIBarButtonItem in VC2's nav bar, I call
[self performSegueWithIdentifier:#"SomeString" sender:nil]
which corresponds to an unwind method inside VC1. For some reason, the transition does not occur.
It only became a problem after switching to XCode 6. It worked fine in XCode 5. Any ideas?
This issue has been doing the rounds and I have the exact same problem. Unfortunately there is no good solution to it yet, other than go back to the old delegate pattern.
If you subclass your parent view controller navigation controller and implement - (UIViewController*)viewControllerForUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender you will see that the modal is actually looking for your unwindSegue method on the navigation controller instead of the view controller that presented the modal.
The problem gets even more amplified if you have a container view controller as the method above gets called all the way up the controller chain to the storyboard's initial view controller.
There's a potential workaround here Unwind Segue not working in iOS 8 but it has its downsides and side effects as well.
Just for the purpose of learning some particular aspects of xCode, I am creating a simple app that has 2 functional view controllers. Each contains a button that can be pressed to switch to the other. I am not using segues. I am using pointers retrieved from the app delegate.
visual illustration (click for higher resolution):
When the app loads, the root view controller presents view 1. When you click "switch to view 2," the following code causes view 2 to appear:
- (IBAction)buttonPressed:(id)sender
{
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
[self presentViewController:appDelegate.view2 animated:YES completion:nil];
}
So far, so good.
But when you click "switch to view 1" on the second view, this same code (replacing "view2" with "view1") gives the following error:
Application tried to present modally an active controller.
So to summarize (where --> = presents), we have root --> view1 --> view2 -x-> view1
I don't care about retaining the history of who presents whom. I simply want the buttons to bring to the top (make visible) a previously displayed view controller, maintaining the state of its views.
It would be nice to know the following:
Is there a workaround that would enable me to achieve the intended behavior using presentViewController? E.g., root --> view2 --> view1
What other method(s) would be more practical for achieving the desired behavior? It/they must use the app delegate because in my real application that will be unavoidable.
Am I breaking the rules by trying to put a view controller on top without integrating into some larger architecture? E.g, is this sort of behavior supposed to be handled by navigation conrollers and pushing/popping? If so, can you explain why xCode doesn't want me to do this? Why can't I just display whatever view controller I want, without it necessarily having any relationship to other view controllers? (Maybe because that could lead to abuse of the app delegate?)
What does it really mean to "present" a view controller? What functional limitations or capabilities does it entail beyond creating pointers between presenting and presenter? What is the importance of leaving the presenting view controller "active"?
If instead make the button on view1 send the presentViewController message to the root view (which I hoped would just change the presentation chain from root --> view1 to root --> view2, leaving view1 still existing in memory but not part of this chain), I get a different error: "Attempt to present on whose view is not in the window hierarchy!" What does this mean? I can't find an explanation of window hierarchy.
Okay, I know I'm asking a lot here, but any amount of enlightenment will be greatly appreciated!!
The correct way to do this is to get the underlying rootVC to do the presenting and dismissing (as you attempt - without the dismissing part - in point 5). You can achieve this by sending a message + completion block back to the rootVC from each of view1 and view2 when you want to present the other.
When you are in view1:
- (IBAction)buttonPressed:(id)sender
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
UIViewController* presentingVC = self.presentingViewController;
[presentingVC dismissViewControllerAnimated:YES completion:^{
[presentingVC presentViewController:appDelegate.view2
animated:YES
completion:nil];
}];
}
and similarly for view2. Take care that you need this line:
UIViewController* presentingVC = self.presentingViewController;
as you can't refer to 'self.presentingViewController' inside the completion block as it's controller has been dismissed at this point.
I think that answers points 1 and 2.
To answer point 3 "Why can't I just display whatever view controller I want, without it necessarily having any relationship to other view controllers?" - well you can (via the rootViewController property of the window), but then you are going to have to implement navigation and manage your viewController pointers, which means you will end up creating a controller of some sort. Apple is helping you here by providing you with a few which cover most needs.
As regards your point 4 - the presenting of a viewController is controlled by the presenting VC, which is why you want to keep that one 'active'. When you send this message:
[self dismissViewControllerAnimated:completion:], self just reroutes the messge to it's presentingViewController. If you get rid of your presentingViewController your dismiss method will break.
Point 5 is answered above. You need to dismiss the topmost view first before asking an underlying view to present. Note that view1 is "still in memory" but only because you have retained a pointer to it in your app delegate.
update
As you are trying to get this to work with an initial launch-straight-to-view1, you could make a BOOL launched property and check/set it from your rootViewController's viewDidAppear:
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (!self.launched) {
self.launched = TRUE;
AppDelegate *appDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
[self presentViewController:appDelegate.view1
animated:YES
completion:nil];
}
}
Let me try to tackle your points one by one.
1) No, you shouldn't do this all with presentViewController.
2) If you want to do root --> view1 --> view2 --> view1, then you don't do that all with presentViewController. To go from view1 back to view2 you should use dismissViewControllerAnimated:completion.
3) The view controllers do have a relationship when you use presentViewController:animated:. The presenting controller has a pointer to the one it presents, and the presented one has a pointer to the one that presented it. So, you're getting these relationships whether you want it or not. There is a way to display whatever controller you want with no relationship between them -- just reset the window's root view controller. The old view controller will be deallocated (if you don't keep a strong pointer to it), and the new one becomes the window's root view controller.
4) Presenting a view controller makes that controller a modal view controller -- it takes over the whole screen and is intended to be used as an interruption in the flow of the app. You really shouldn't use them extensively to go from one controller to another (and especially not for going "backwards" to previous controllers). Because of the way it's supposed to be used, you normally want to go back to the controller that presented it, so that's why it's kept "active" (in the sense that it's not deallocated).
5) You get that error because root's view is not on screen, view1's is. You need to present a view controller from the controller on screen.
Hi there and thank you in advice for your help. I have a really strange problem while working with ViewControllers in Xcode4. First of all I have to say that I'm not using storyboards and I prefer to create any UI element programmatically. So I've set a UIButton and I want that, when pressed, it brings me to a new view controller. This is the code I'm using for a button:
-(void)settingsAndExportHandle:(UIButton *)buttonSender {
SettingsViewController* settingView = [[SettingsViewController alloc] initWithNibName:#"SettingsViewController" bundle:nil];
settingView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:settingView animated:YES];
}
This buttons is initialized and allocated in the viewDidLoad method of the RootViewController. I want to switch to the other view controller (in this case SettingsViewController) when I press the button.
The strange thing is that when I press the button, the animation that flips the controllers goes well, but when it finishes I obtain the EXACT same things that I had on the RootViewControllers (same custom views, same buttons, same all!). The question is: what I'm missing?? I have to say that I use ARC (automatic reference counting) so I can't release or dealloc the views and buttons I've created on my RootViewController.
Any help will be appreciated. Thank you all!
Pushing and and modally presenting view controllers does not deallocate the view controller that presented them. It simply adds the additional view controller to the stack. You'll need to implement a callback method so that when the user hits the button to flip back to root view controller, your settings view controller lets the root view controller know what's about to happen so you can call a method you've written to reset the interface back to whatever state you need it at. You may also be able to use viewWillAppear: but that's a little messy.
However, according to the Apple Human Interface Guidelines, the user expects that when they push a view controller or modally present it, the view controller they were on will save state and be exactly the way they left it when they came back. It's disconcerting and annoying when state is not preserved while in a navigation controller context. It is especially annoying when it's modally presented.
Think about this - A user is in a hypothetical Mail app. They start typing out an email and set a font size and a color. They tap on the add attachment button, which brings up a modal view controller that allows them to select a picture. They select the picture and the modal view is dismissed, and in your implementation, the mail composing interface would have reset and the email content would be gone or at the very least the selected font size and color would be back to the default. That's not a good experience.
I have a UINavigationController within one of the tabs of a UITabBarController.
I now present a new view controller (let’s call it Steve) over the whole app (using presentViewController:animated:completion:).
Then, I simulate low memory.
After dismissing Steve (using dismissViewControllerAnimated:completion:), I can now see that the UINavigationController’s view is gone; within the tab; only an empty white area is seen!
Why is this? I have tried calling the view methods on all imaginable controllers upon Steve’s dismissal, but the contents of the tab still stay empty (white).
The strange thing is this: If I click on another tab, and click back on the original tab, the contents (the navigation controller) shows just fine again. Is the tab bar controller doing something special to force the view to display?
UPDATE: I was able to “fix” my issue with this terrible code, just before dismissing Steve:
[[[[[self tabBarController] view] subviews] objectAtIndex:0]
addSubview:[[self navigationController] view]];
What this does is that it finds the subview of the tab bar controller which is not the tab bar (i.e. the top view), and then adds the navigation controller’s view to be its subview.
This is of course terrible, because it makes internal assumptions about the subview structure of the tab bar controller’s view.
If someone has any better solutions, please let me know about them.
When your app receives a memory warning, one of the first thing it will do is drop the view hierarchies of any view controllers that have loaded views but are not currently visible (like your UINavigationController). Most likely, whatever view controller is at the top of your navigation stack is having its views dropped but not reloading them when it reappears.
Always put your view construction code in -loadView or -viewDidLoad and not in -init. That way, the view controller will reconstruct your view if it's been dropped due to a memory warning.
(P.S.: The reason your hack works is the bit where you call [[self navigationController] view], which in turn calls -loadView on the top VC in the stack, forcing it to reconstruct its view.)
I am trying to implement a popover view in a tableview controller. My intention is for the user to select an option from the table list as shown below.
Note that my popover view actually displays the data from a separate table view controller. I am creating the popover view controller via the following initialization method
self.popOverViewController = [[UIPopoverController alloc]initWithContentViewController:optionsTableViewController];
After the user selects an option for example "Hottest All Time", the control should be passed from the tableview Controller (in the popover view) back to the MAIN table view controller (parent view) so as to trigger a table reloadData method.
Query: Is there a way to return the control from the tableview controller in the popover controller back to the MAIN tableview controller?
Do I have to use a delegate method to do this?
The two approaches I've seen are roughly the standard sort of fare:
create a delegate protocol for the class type of optionsTableViewController, have the controller that creates the popover implement it and set itself as the delegate when issuing the popover
use the NSNotificationCenter (which actually fits the intended purpose of the thing if you've a one-to-many message, as may be the case if you've a popover with a setting that affects a bunch of different controllers and you don't really care which is visible when the user requests the popover)