I have a segue that should take place when one of a number of things happen, so it's called programatically, like so:
- (void)unwindAway
{
NSLog(#"Let's segue");
[self performSegueWithIdentifier:#"mySegue" sender:self];
NSLog(#"We should have just performed the segue");
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSLog(#"Let's do a segue");
}
but the output I get in the console is:
2014-05-29 22:20:30.173 My App[7848:60b] Let's segue
2014-05-29 22:20:30.178 My App[7848:60b] We should have just performed the segue
so as you can see, it's not even calling prepareForSegue.
The segue name is correct - if I give an invalid segue name it errors as you'd expect.
Any ideas?
For unwind segues, prepareForSegue:sender: is called on the view controller that was the source of the segue, in other words the one you're exiting from.
As per Rob's suggestion in the comments I checked the name of the method in the destination View Controller in the segue. It looked right (and was selected in IB rather than typed) but pasting over it and recompiling fixed the problem. Something must have been messed up in the source code of the storyboard, perhaps an artefact of renaming methods.
It's worth noting if anyone has a similar issue that the app won't generate any error if the destination method of a segue isn't found anywhere (I've confirmed this by typing a nonsense method name).
I know that your issue has been fixed, but for future reference I just want to say that similar problems might be caused by the way the unwind process works (zie the technical note link above).
As soon as the segue has been triggered in a certain view controller, its parent (!) view controller is called with the message: viewControllerForUnwindSegueAction:fromViewController:withSender:. The implementation checks if the parent wants to handle the unwind action. If not, it's array with child view controllers is searched for a view controller that wants to handle the action.
My problem was that the unwind action was implemented in a child view controller of a view controller that was embedded in a navigation controller. So, when te segue began, the navigation controller (the parent) was asked: will you handle the action? It returned NO. Then, it's children were asked the same. It returned NO. Because the message isn't sent to a child view controller of a child of the parent view controller, there isn't a view controller that will handle the unwind action and it is aborted without an error message.
My solution was to implement the unwind action in the view controller itself and not in it's child view controller.
Related
Unwind segues seem not to behave as expected in iOS 8.1 when combined with a modal view and container view.
Here's the view controller hierarchy for the test project which can be found on github:
Tapping on the "tap me" button pushes the modal view which is embedded in a navigation controller and which has a tableView as a child view controller. Tapping on a row in the tableView pushes another tableView. Finally, tapping on a row in this final tableView should call the unwind segue named bUnwindSegue found on the previous view controller.
Problems:
bUnwindSegue is never called.
According to technical note TN2298 a container view controller is responsible for selecting the child view controller to handle a segue. In this case viewControllerForUnwindSegueAction:fromViewController:withSender: should be called on the container view controller. It isn't.
In the example project, you can see that BTableViewController contains the unwind segue:
- (IBAction)bUnwindSegue:(UIStoryboardSegue *)segue;
{
NSLog(#"Unwinding...this unwind segue will never get called.");
}
In the storyboard, the cell selection action for CTableViewController is indeed the bUnwindSegue. Also note that if you change the cell select action of CTableViewController to the unwind segue in the container view controller -- containerVCUnwindSegue -- that the segue is called correctly.
Are unwind segues not behaving as expected?
(1) You're misunderstanding the technical note TN2298 you cited and (2) you're not overriding viewControllerForUnwindSegueAction: appropriately.
As the TN2298 doc section you linked to about Container View Controller states underneath its "Selecting a Child View Controller to Handle An Unwind Action" subheading:
Your container view controller should override the method shown in
[viewControllerForUnwindSegueAction:] to search its child
view controllers for a view controller that wants to handle the unwind
action. If none of a container's child view controllers want to handle
the unwind action, it should invoke the super's implementation and return
the result.
First off, to override the method, you have to subclass the UINavigationController in your storyboard and add the viewControllerForUnwindSegueAction: method there. After doing that, you'll see the method is now being called as expected.
But your second error is that your current attempt to override the viewControllerForUnwindSegueAction: method simply contains return self;. You should instead be returning the view controller that you'd like to handle the unwind action.
So say, for example, you have a public variable in VCWithContainedVCsViewController to hold the current instance of BTableViewController and you access you're able to access that current container view controller, ex:
- (UIViewController *)viewControllerForUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender
{
NSLog(#"Technical note TN2298 indicates child VCs defer to their parent to determine where an unwind segue will be handled.");
if ([NSStringFromSelector(action) isEqualToString:#"bUnwindSegue:"]) {
NSLog(#"%#", self.viewControllers);
VCWithContainedVCsViewController *containerVC = (VCWithContainedVCsViewController*)self.viewControllers[0];
return containerVC.container;
}
return [super viewControllerForUnwindSegueAction:action fromViewController:fromViewController withSender:sender];
}
What you'll see in that case is that bUnwindSegue: is in fact being called (your message should print), but the segue still won't happen.
Why is this?
Because as I mentioned in the comments, BTableViewController is not on the current navigation stack. Some child view controllers of BTableViewController, like CTableViewController, will be on the navigation stack because, for example, CTableViewController is not a container view. But BTableViewController itself is not capable of performing the segue on its own because it is not on the current navigation stack. So although you can in fact select some child view controllers to handle unwind actions as the documentation states, BTableViewController isn't going to be one of them.
In UINavigationViewController, if I wanna pass values from one controller to next, just call - (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender , but what should I do if I wanna pass values from one viewController to previous viewController
I remember running across this same issue a few projects back. I can't find the my code to answer this question, but I did find a few tutorials.
http://prateekvjoshi.com/2014/02/16/ios-app-passing-data-between-view-controllers/
http://www.infragistics.com/community/blogs/torrey-betts/archive/2014/05/29/passing-data-between-view-controllers-ios-obj-c.aspx
and hence the concept of delegate came forth from segues.
Basically Segues are transition from one view to another but the child view is over the parent view, (inside a stack) so the parent view is still loaded.
So if u put segues everywhere and pass values between them then objects will keep on be creating and stored inside a stack and thus the cycle carries on.
So delegates was introduced.
Delegate is a method by which a child view controller(the later one) sends information using the inbuild delegate methods or self created protocol methods to the Previous view controller(the first one).
Here the one sending the information(later view) declares a delegate object, and a delegate method.
Which is then implemented by the recieving class(first view). So even after the later view is popped from the stack, the information is sent back to the root view by the delegate method.
Go through the documentation, its given in a more appropriate way
Hope this helps
Set previous view controller as delegate of current view controller and pass any values you want. This is standard approach.
In my app I have a manually triggered push segue called "details". It is wired from the source controller to the destination controller and it's identifier is set. In my code I call
[self performSegueWithIdentifier:#"details" sender:sender];
I can see the prepareForSegue function firing and have verified that the destination controller is of the proper type. I pass the destination controller a few bits of data it needs to display correctly. Oddly enough NOTHING happens. The app does not go to the destination controller.
Here are some things to look out for if you are having an issue with performSegueWithIdentifier while using a storyboard
You have correctly hooked up your transitions in your Storyboard
Your Segue Identifier matches the one on your Storyboard
Your ViewController's class has not become deallocated
(This can sometimes occur accidentally by calling from another class)
Try setting sender to "self" rather than sender
Also, if you are planning on passing data between ViewControllers it is suggested that you use the prepareForSegue method, allowing you to pass values before segue'ing.
I have master view controller (derived from UINavigationController) which seques to view controller A.
When the user exits A an unwind seque returns to the master controller which then seques to view controller B.
The problem I am facing is that if I have the following code in the master view controller:
- (IBAction)unwindToMasterViewController:(UIStoryboardSegue *)segue
{
[self performSegueWithIdentifier:#"SequeToViewControllerB" sender:self];
}
Then I get the error: "nested push animation can result in corrupted navigation bar Unbalanced calls to begin/end appearance transitions for View Controller B".
However if I remove the call to the performSeque from within the unwind seque and trigger it manually from a button on the master view controller then everything is ok. Therefore this suggests the problem is timing related, and in fact I've seen similar problems like this in the past which were related to animation timing (trying to call pushViewControllerAnimated:YES before a previous call to push has totally completed etc.).
So bearing that in mind I tried putting the seque in the code below, expecting didPopItem wouldn't get called until view controller A had completed being popped off the stack.
- (void)navigationBar:(UINavigationBar *)navigationBar didPopItem:(UINavigationItem *)item
{
[self performSegueWithIdentifier:#"HomeSeque" sender:self];
}
However that didn't solve it.
So how can I tell when view controller A has finished being popped off the stack so I know its safe to seque to view controller B? (Assuming that is indeed the problem, but seems like it is due to my button experiment).
Alternatively is there a way I can get the OS to transition from VC A to VC B for me?
You can use the approach discussed here: popping and pushing view controllers in same action
In other words, instead of using the canned unwind segue, which will call popViewControllerAnimated:YES, you pop by calling popViewControllerAnimated:NO and now you can go straight on to a push segue.
An even cleaner way is to call setViewControllers:animated: with the new stack of view controllers.
I have a table view controller embedded in a container in a view controller.
In both the view and table view controllers prepareForSegue method I put NSLog(#"name of the controller")
I see a log for the view controller but not the table view controller. Shouldn't I also see the nslog for my table view's prepareForSegue?
Exactly - as Greg explains, the embed type of segue is
only called during setup!
This is very confusing. You could say that
"prepareForSegue" is A REALLY BAD NAME!
the name "prepare for segue" only makes sense in the (rare!) case where you are actually "segueing" from one scene to another
In an iOS app container views are commonplace, you have them everywhere, whereas you rarely use an actual "scene segue".
So really, "prepareForSegue" should be called something like:
"Hey, we're setting up all your container views -- you can grab any info you need at this time! Oh, if you happen to be doing a scene segue, you can use this also!"
what about:
containerViewBeingSetUpOhAndAlsoPrepareForSegueIfYouHappenToBeDoingThat:
That's a bit long, but clearer!
It's just one of those weird things about iOS that you have to know, but is never explained anywhere.
Here's a full explanation of using container views for beginners https://stackoverflow.com/a/23403979/294884
In Your Log Frame View Controller prepareForSegue will be called directly after initWithCoder: to prepare your TableViewController. I cannot see your connection between table view controller and another view (view on the right) but I believe it will be called when you hit the row.
//EXTENDED
The other thing could be that you haven't add UINavigationController on the view hierarchy and you set up your segue style to 'push'. Try change style of your segue to 'modal' in attribute inspector.
Hope this help.
Other than what's already discussed, you should make sure you aren't ignoring segue identifier in following delegate call.
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender