I have a ViewController that has a child controller with a dynamic tableview.
On tapping a table cell I need to segue to a new view.
I've tried all kinds of methods, including directly control dragging from the cell (resulting in a 'detached controller' warning) with little success and the current situation is as follows.
Child Controller (currentName, currentType are globals that can be picked up by the parent, myHome)
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
currentName = "John"
currentType = 1
let segueVC = myHome()
segueVC.segueToMain()
}
Parent Controller (myHome)
func segueToMain() {
dispatch_async(dispatch_get_main_queue()){
self.performSegueWithIdentifier("home_myMain", sender: self)
}
}
I know this is probably horrendous but it appears to work fine in that it stores the data and gets to the parent function. However the attempt to segue causes the 'Receiver has no segue with identifier' error. The segue id is exactly correct. It links to a Navigation Controller.
I have a feeling that I need some delegates etc but I'm not really sure how to deal with them. Getting rid of the navigation controller and segue direct to view didn't make any difference.
Any help with how to get this segue to trigger would be greatly appreciated.
EDIT
In relation to answers suggesting the segue direct from the child, here is the setup which seems to result in a 'detached' controller error. For the record, I would love to use this method but all attempts failed.
The Parent Controller (A) builds a PageMenu class that gets its page structure from Controller B. B has a table with cells directly segued to another view.
A constructs the menu as follows
self.addChildViewController(self.pageMenu!)
self.view.addSubview(pageMenu!.view)
self.pageMenu!.didMoveToParentViewController(self)
PageMenu uses class B as the template for each menu page.
There is no physical link between A and B on the storyboard - only that the class for A sets B as a child.
Segues from B fail with a 'detached controller' error so maybe there's something I'm missing from the child setup?
Thanks
You can't just allocate a new instance of your parent view controller and ask it to perform a segue - As you are instantiating the view controller directly, it won't be associated with the storyboard, this is why you get an exception stating that that view controller doesn't have a segue of the specified identifier.
Even if you did instantiate a new instance from the storyboard you would still get an error as that new instance wouldn't be on screen, resulting in a message that you attempted to present a view controller on a view controller that isn't in the view hierarchy.
You need your child view controller to have a reference to the parent view controller instance that contains it and then invoke the segue using that instance.
Also, there is no need for the dispatch_async - the tableview delegate method will be invoked on the main queue already.
Finally, by convention, class names start with an upper case letter, so myHome should be MyHome (or even better, something like MyHomeViewController)
Related
I created a scene and tried to link the class to the scene and create a segue with the storyboard and use self.performSegueWithIdentifier("SegueID", sender: self) and no matter what I did (i.e. clean build) I still got the same error "reason: 'Receiver () has no segue with identifier 'SegueID''"
I think the best way to solve this is to avoid the segue in this instance all together.
So is there a way to make a call from a view in the code to transition to another view without using segue in swift?
Edit
I've tried all three ways but to no avail. The most common way of just creating a segue between two scenes in storyboard and giving it a name, in my case "Details", but the segue isn't recognized.
So I tried the next way of loading it from a nib and then pushing that nib onto the navigation stack, but when compile and build the program and I click on the button to present to new view controller, nothing happens except for the function println executing.
And trying to use the destination controller manually just didn't work. instantiateViewControllerWithIdentifier expects a string and complains that there are too many arguments in the call
Your code doesn't work because you need to setup a segue in storyboards with a specific ID that you pass as an argument when you call performSegueWithIdentifier. So, in your case, you need to create a segue with ID "SegueID", since that is the string that you are passing to that call.
If you don't want to do this using storyboards, you can still just use UINavigationController directly (after all, a segue uses a UINavigationController under the hood).
Then you just need to instantiate your destination view controller manually (e.g. by using
self.storyboard.instantiateViewControllerWithIdentifier(<your vc id from the storyboard>, animated:true)
or by loading it from a nib
init(nibName:bundle:)
and then push it onto the navigation stack using
self.navigationController.pushViewController(<the formerly instantiaded vc>)
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.
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.
I have a tableview that is within a container view. When the user selects any of the rows, a method is called in the parent controller, which tells the parent controller to perform a segue. However, I am unable to figure out why it doesn't work. The code gets called from the didSelectRow -function in the tableView. The method does perform, but it gives me an error about no segue with that identifier.
However, when I call the method(listJobsOfSite) from within the parent view controller it works.
(tableview)
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.jobVC listJobsOfSite:#"locPwn"];
}
parent view controller
-(void)listJobsOfSite:(NSString *)site
{
[self performSegueWithIdentifier:#"JobSegue2" sender:nil];
}
EDIT:
The segue is between view controller 1, and view controller 2. view controller 1 holds a container view, which again holds a table view controller. This tableview controller should tell view controller 1 to segue into view controller 2.
EDIT 2:
screenshot http://tinypic.com/r/30x7dr9/8
You don't call classes. The word you are looking for is "method." You call your listJobsOfSite method that is in your parent view controller class .
Have you made sure you have given the segue an identifier? In your Storyboard you should click on the segue, inspect it, and enter "JobSegue2" in the field marked "Identifier."
If you have done step 2, do you need a container view? What are you trying to accomplish with the Container View + TableView? It sounds like your design would make more sense without the container view and the TableView as a property of View Controller 1.
If your heart is set on using the container view, in your Storyboard try to Control + Drag from your TableView cell to your View Controller 2. Name that segue "JobSegue2." Then you don't need to call any methods in your didSelectRow method. You also don't need a storyboard segue from View Controller 1 to 2. It seems like this question has the behavior you want (the question, not the answer! He is having the opposite problem you are).
Edit: Just noticed you have a Navigation Controller within your container view, so my suggestion above will likely push View Controller 2 within the container view. I'm totally confused by what you're trying to do.
I'm working on an app that I need to change the text of text view of child view controller from a method in the parent view controller.
I have a button in child VC, when it's pressed, the view doesn't change, but a method in parent VC is called, and another method in parent VC will change the text of text view in child VC.
[self.delegate buttonPressed];//in child VC, call method in parent VC
It was working before I use storyboard.
In the parent vc, I just had code below,
childViewControler.textViewName.text=#"something";
Now I changed to storyboard, by using NSLog, I know the method to change the text is called. But no text is displayed.
I'm thinking I might add something since I am using storyboard now?
Can someone give me advice?
Thank you.
The childViewControler variable in your parent view controller needs to point to the instance of your child view controller class created by the segue. If you don't assign anything to it, it's nil, and any messages to it do nothing.
It's unclear from the code you've posted whether it's a property, instance variable, or local variable; here's how you could do it with an instance variable:
#implementation ParentViewController
{
ChildViewController *childViewController;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
// if you have multiple segues, check segue.identifier
// so you only do this for the correct segue
childViewController = segue.destinationViewController;
}
- (void)buttonPressed
{
childViewController.textViewName.text=#"something";
}
#end
The key bit is in prepareForSegue:sender: -- you need to make sure that the childViewController variable points to the view controller created by the segue.
It's also possible that your problem results from still having some old pre-storyboard code that allocs and inits a child view controller and puts it into your childViewController variable. In that case, you'd have two instances of that view controller class around: the one created by the segue and the one you made. Only the one created by the segue is hooked into the UI, so any changes you make to the other won't be visible. You can solve this problem by getting rid of any code that creates and presents the child view controller -- the storyboard takes care of all that now, and prepareForSegue:sender: is the only place you need to get a reference to the child view controller.