On my app's start up, it programmatically shows a LoginViewController using a segue. The view controller is presented modally with transition set to cross dissolve. Upon successful authentication, I want to dismiss the login view by programmatically triggering an unwind segue. So I added this to my header file:
- (IBAction)unwindSegue:(UIStoryboardSegue *)segue;
now in IB I'm able to control-drag from the "File's Owner" LoginViewController to the Exit button and choose unwindSegue:. This creates a manual segue, it shows up in the Connections inspectors for the File's Owner and the Exit button correctly. I then click on the newly created Unwind segue from the scene in IB and then give it a name. If I click on the "go to" button for the unwind segue action it takes me to the declaration mentioned above.
So far so good, I then trigger this unwind segue upon successful authentication in my GCD block:
....
dispatch_async(dispatch_get_main_queue(), ^
{
[self performSegueWithIdentifier:#"UnwindSegueIdentifier" sender:self];
[self.spinner removeFromSuperview];
self.spinner = nil;
});
.....and nothing happens when it runs. The spinner does get removed correctly, but there's no sign of that unwind segue executing.
A break point in the implementation of unwindSegue: never gets hit. There are no errors thrown. Nothing gets written to the console. The identifier is correct, I triple checked (otherwise it will fail anyway).
I looked at the answers here, here and here but I don't seem to have missed anything.
What I did notice though, is that Xcode thinks unwindSegue: is not linked:
I'm unable to drag from the little empty circle in front of unwindSegue: and link it to the Exit button.
Any help will be appreciated.
If you are using a modal segue to go to your login view, all you need to go back is to call
[self dismissViewControllerAnimated:YES completion:nil];
More precisely, you should call it at the presenting controller (your first controller), but it will be forwarded if you call at at the presented controller. You can use the completion block do any required clean up. There is no need to use GCD.
EDIT
To answer the additional comment: I'm not really sure from your description, but it seems you've implemented the unwind action at the presented controller instead at the presenting controller. Unwind segues are to allow to do something at the caller (e.g., setting data) without an additional protocol.
Quoting text from Apple's Technical Note on Unwind Segue:
To add an unwind segue that will only be triggered programmatically, control+drag from the scene's view controller icon to its exit icon, then select an unwind action for the new segue from the popup menu.
Link to the Technical Note
Related
I have an iPad app which has a lot of screens and a lot of segue options.
At the moment, I am simply using performSegueWithIdentifier to initiate these segues, and my fear is that I'm taking up a lot of memory as the user performs more and more segues.
I've seen that people recommend using the function popToRootViewControllerAnimated: if using a UINavigationController, but the problem is that I'm not using one.
How can I stop the number of VC's proliferating?
The way the app works, the user does constantly return to the root VC - effectively a search screen. So if I could clear the stack of VC's when such a segue is needed, that would solve my issue I think, but I have no idea how to go about this.
Thanks for any suggestions.
When you are using segues the flow moves backwards and forwards. When the user moves backwards (ie presses "back") then it will not push to a new VC but it will pop to a VC that already existed. When you pop, the current VC is removed from the stack and memory.
If you have segues to move backwards in the flow then this is wrong. You only need segues to move forward.
A PROPER PREPARE FOR SEGUE
In prepare for segue you should never create your own view controllers and push to them. The storyboard is there to do all of this for you.
A proper prepareForSegue method should look something like this...
- (void)prepareForSegue:(UIStoryBoardSegue*)segue
{
if([segue.identifier isEqualToString:"SomeSegue"])
{
MyNewViewController *controller = segue.destinationViewController;
controller.someProperty = "some value to pass in";
}
}
That is all you need. Note that you only need this if you intend to pass some information to the new view controller. If you are not passing anything forward then you don't need this method at all.
When the method ends the new VC will get pushed onto the screen by the storyboard file.
UNWIND SEGUES
If you have a random flow (like in your comment) then you can use unwind segues to achieve this.
In you 'A' view controller have a function like...
- (IBAction)someUnwindAction:(UIStoryboardSegue*)sender
{
//some action to run when unwinding.
}
It needs to receive a UIStoryboardSegue object. If set up as an IBAction you can also access it from Interface Builder.
Now when you want to go A > B > C > B > A then just used the standard push and pop (from the segue and back button).
When you want to go A > B > C > A then you can use the unwind segue from controller C.
If you have a cancel button or something in controller C and this is in the Interface Builder and this should take you back to controller A. Then in the Interface Builder underneath controller C you will have a little green square with a door and an arrow pointing out of it. Just point the action of the cancel button to this symbol and choose "someUnwindAction". (Note, unwindAction is in A, button is in C.) XCode then uses this to pop you all the way back to A and deals with removing any memory and stuff. If you want you can send additional information back to A too.
If you want to access this unwind segue from C programmatically then you can run...
[self performSegueWithIdentifier:"someUnwindAction" sender:nil];
This will also pop back to A.
imho I don't see any issues with using segues, it is much more simple than anything else. If you worry about memory consumed then just profile your app and see how much it eats and how often your "memory pressure handler" is called.
ok I believe I've figured this out. It appears to work.
Since I keep returning to a common VC, I am placing code in the places where I want to segue back to this 'root' VC to clear the VC stack.
I still perform the actual segue in my normal code
[self performSegueWithIdentifier:#"editBackToSearch" sender:self];
However in the 'prepareForSegue:' method, I no longer create a new VC object, instead I perform any work I have outstanding (save my data), and then clear the VC stack with the following code.
UIViewController *vc = self;
while ([vc presentingViewController] != NULL)
{
vc = [vc presentingViewController];
}
[vc dismissViewControllerAnimated:NO completion:nil];
It appears to run smoothly and has the desired impact (confirmed via the profiler) of releasing the memory that these VC's sucked up.
One last comment - for some reason I could not animate the dismissal command, this appeared to trigger an infinite loop and resulted with an EXC_BAD_ACCESS. With animation set to NO, it works great though.
Thanks for your help.
Swift 3: this is to prevent memory leaks in Swift 3 & 4
#IBAction func entryViewSelected(_ sender: UIButton) {
self.dismiss(animated: true, completion: nil)
}
For some reason, when I try to open a view controller via modal segue, it opens up two of the same type. Why is this happening?
Warning: Attempt to present <ModalViewController: 0x7fa062c5edd0>
on <HomeViewController: 0x7fa062e16e40> which is already presenting
<ModalViewController: 0x7fa062fb9780>
This is causing problems because I try to use delegates, but my main view controller never gets the correct delegate.
The issue occurs when I click the the button which triggers showModalView
HomeViewController
- (IBAction)showModalView:(UIButton *)sender {
ModalViewController *modalView = [[ModalViewController alloc] init];
[self presentViewController:modalView animated:YES completion:nil];
}
I tried this solution here and here and a dozen other ones, but none seem to work for me.
Why is this happening?
The problem you're having, is because you've connected a segue to the button, and are also presenting the controller in code; you should be doing one or the other. When you removed the segue, you got a black screen because you're using alloc init to create your controller. If you made the controller in a storyboard, then you should use instantiateViewControllerWithIdentifier: instead.
However, the easier way would be to leave the segue connected to the button, and delete the code you have in the button's action method. The button doesn't need an action method, if you have it hooked directly to a segue. All of this is covered in Apple's document, "View Controller Programming Guide for iOS". You should read it.
I have a problem in iOS7 where I am calling a segue with performSegueWithIdentifier (I have code just like this that works just about everywhere else), then I log the segue in prepareForSegue, then I log again the view controller (VC) that the segue is supposed to push to the top.
prepareForSegue gets called appropriately and the segue has the correct string as its identifier property. Yet the VC that it is supposed to push to the top never gets initialized nor viewWillAppear gets called.
The segue I am talking about, which is the only one that does not work (all the other ones work in both ios6 and 7), is the one leading form the center VC to the right VC. By the way, this works flawlessly in iOS6.
What could be the cause?
the code:
-(IBAction)gotoMainMenu:(id)sender{
DLog(#"DifferentName");
[self performSegueWithIdentifier:#"DifferentName" sender:self];
}
Get in the habit of not wiring up segue's to buttons. Wire them up to the VC and then in the touchUpInside action, fire off the performSegueWithIdentifier.
I had the same issue and solved it as follows. In view A I had a segue that was triggered by a button (UIButton) and the button was also connected to an action in my controller. When I clicked the button in View A, View B would appear as expected. However, when I tried clicking a button in View B to go to View C nothing happened just as you described above.
In my case the issue was resolved in View A. I removed the segue that was tied to the button and let the IBAction that was associated with the button handle calling the performSegueWithIdentifier, then I created a new manual segue that was only tied to the view and voila things worked as expected again.
In short, I guess make sure you don't have both and action and a segue linked to the same button. Hope this helps.
After struggling for days on firing a segue conditionally, I managed to solve it thanks to Simon's answer here. Please take a moment to have a look or you might not understand what I'm talking about below. I didn't copy paste his answer because he's already explained it nicely over there.
Now I've faced a new question. What if I have multiple View Controllers that I want to segue to from one View Controller?
To explain it further : Say I have one MainViewController with 2 buttons. When clicked upon each button, it should segue to their respective View Controller. First button to FirstViewController and the second button to SecondViewController.
The method described in Simon's answer can be used when you segue from one View Controller to another View Controller. Since in that method, you tie the segue to the View Controller itsrlf and not to the button, you have only one segue with an identifier for that particular View Controller. Therefore I cannot distinguish between the button taps separately.
Is there a workaround to solve this problem?
Thank you.
It might be bit premature to say this but I guess you should look into Segue more deeply.
Yes you can perform segure from button. Just control click the button and drag the cursor to view controller you want it SEGUE'd. And from my understanding only condition there is each button tap results a segue to a fixed view. There is no condition there.
Also, you can push the navigation controller manually by
YourViewController *destViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"YourDestinationViewId"];
[self.navigationController pushViewController:destViewController animated:YES];
UPDATE:
prepareForSegue is too late to stop a segue from proceeding. Yes you can create multiple segues from your view to other view controllers. And in this case you have to do so. Don't reate a segue from button, just define a IBACtion on the button click you can do the validation from there,
if(validationSuccess) {
[self performSegueWithIdentifier:#"segue1" sender:self];
}
if you are using ios6
- (BOOL)shouldPerformSegueWithIdentifier:(NSString *)identifier sender:(id)sender
return YES on validation success and NO on failure to stop it from proceeding.
I suggest you look a bit at reworking your code logic.
If I understand correctly, you have a VC (embedded in a Nav. Controller) with 2 buttons and you have figured out how to segue each button to a different VC.
Your problem is you want to make sure that even if one of the buttons are pressed, a validation is done before an action takes place. I would advise this is bad User Interface design because the user has the illusion that this button might do something and then they click it and nothing happens.
UIButton can be connected to IBActions (to initiate actions) and IBOutlets (to set their properties). If this is a button created in IB directly, I would connect it to your class as an Outlet property:
#property (nonatomic,weak) IBOutlet UIButton* myButton;
And then set its enabled value:
self.myButton.enabled=NO;
This will keep the button and dim it. This is much better UI design and the user knows they should not press the button because some condition is not satisfied.
I would rework the code so that you set this value as disabled by default for example and enable it appropriately in your code whenever your "condition" is satisfied.
Obviously if this button is created programmatically (in your code without IB) then it is easy to just use the second command above.
Hope this helps.
I just wrote another way to call multiple detail views from a single table. Each cell could essentially make a different view be displayed. The code is similar to what you see in this post but you essentially use identifiers and attributes on the list item to determine which view to show.
https://codebylarry.com/2016/07/15/multiple-detail-views-in-swift/
override func tableView(tableView: UITableView,didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row == 1 {
self.performSegueWithIdentifier("secondView", sender: self)
} else {
self.performSegueWithIdentifier(“others", sender: self)
}
}
I am creating an app using iOS 5 SDK. I managed to push views using the Storyboard's Segues, but I cannot find the proper way to pop the current view and go back to the previous one.
I am not using any navigationController (the app doesn't have any top or bottom bars).
I don't think using modal or push segue the other way would be the solution as it instantiates a new controller.
Do I have to use a custom Segue with the opposite animation and deletion of the view at the end ? Or is there a better way ?
Storyboards in iOS 5 don't provide a "no-code" way to return from a segue -- that's something you'll need to implement yourself.
If you use "push" segues (which require a navigation controller), use the navigation controller's popViewControllerAnimated: method to undo the last push segue. (Or other methods to undo more; see the UINavigationController documentation.)
If you use "modal" segues, call dismissViewControllerAnimated:completion: on the view controller which presented the current view controller (which you can get from its presentingViewController property).
Update: In iOS 6 and later there's unwind segues for going "back" in a storyboard. It's still not a no-code solution -- and it shouldn't be, because you need to be able to do things like differentiating between "Done" and "Cancel" exits from a modal view controller. But it does let you put more of the semantic flow of your app into the storyboard. Apple has a tech note that describes them in detail, and they're also covered in the video from WWDC 2012 Session 407.
You could try calling [self dismissViewControllerAnimated:YES completion:nil]; from the controller you want to dismiss (whether the controller has been pushed, or shown modally).
Here is the related documentation : UIViewController Class Reference
The presenting view controller is responsible for dismissing the view controller it presented. If you call this method on the presented view controller itself, it automatically forwards the message to the presenting view controller.
Just to clarify.
In the class that was pushed. Simply wire up the following and the controller and view will be popped off.
[self.navigationController popViewControllerAnimated:YES];
Create Segue type "Custom" on your stroyboard. This can be from a button.
Create a new UIStoryboardSegue class named "popSegue"
In the popSegue.m file add the following;
-(void)perform{
UIViewController *sourceViewContreoller = [self sourceViewController];
[sourceViewContreoller.navigationController popViewControllerAnimated:YES];
}
-In the storyboard editor.
-Select the segue and change the Segue Class to "popSegue"
-Set the Identifier to "popSegue"
Done!
You can use the same "popSegue" class throughout your project.
Hope this helps
I'm using Xcode 5 also and here's how it's done. First, in the view code file that pushed the other, create an IBAction method in the .h file such as this:
- (IBAction)exitToHere:(UIStoryboardPopoverSegue *)segue sender:(id)sender;
Then in the .m file add this:
- (IBAction)exitToHere:(UIStoryboardPopoverSegue *)segue sender:(id)sender {
}
You can add any cleanup code you want executed in this method. Next go to your storyboard and select the pushed view. I assume you've got some kind of button on the view that the user taps to signal he's finished. Click on that button, hold down the key and drag to the the green box below the view which is the Exit. Release the mouse button but continue to hold the key. A popup will appear and your method will show in the list. Select that method. Now when the user clicks on the button, the view will pop and you'll be returned to the starting method.