trouble understanding Obj-C MVC: exit and unwindSegue - ios

Created a button to exit from a second ViewController to the previous ViewController. I've read in tutorials that this should be by using the "EXIT" in the view controller. (control+click+drag the button to EXIT).
I have created an "exit" button on this second controller, and a method on the ViewController class to hook up with the control+click+drag exit segue. This method is completely empty:
- (IBAction) unwindToMainMenu:(UIStoryboardSegue *) unwindSegue
{
}
And yet, when I press the button it returns back to the previous ViewController as I intended, but I was expecting nothing should happen (as the method has no content).
I assume the StoryboardSegue object passed into this method is this EXIT that I dragged the button into, but there's no code to handle that object.
EDIT:
Also, I am not returning anything, yet the declaration of the method says it should return an IBAction. Why is the compiler not complaining?
Can you help me understand what am I missing?

Welcome to stackoverflow,
Firstly, IBAction is just a way to say 'you can drag to this' for Xcode, it's not really like a return type, i just consider it void.
Secondly, it is working as intended :D Basically you can now call this method like you have with hooking it up with the storyboard or you can manually call it like:
[self performSegueWithIdentifier:#"unwindToMainMenu" sender:self];
and all of the variables that you have set in your current view controller will be passed back to the source view controller that you're unwinding to.
e.g
-(IBAction)prepareForUnwind:(UIStoryboardSegue *)segue {
if ([segue.identifier isEqualToString:#"unwindToMainMenu"]) {
VcExample *vcExample= (VcExample *)segue.sourceViewController;
NSString *string = vcExample.aVariableNameYoureInterestedInFromTheVCYouUnwoundFromLolThisIsLong;
}
}
The unwind segue is just a built in feature, if you hook that button up to it, it will segue

Related

Segue performs even though identifier is not met

i've created a segue form a uibutton to a viewController with the style push. I only want this segue to be pushed if a criteria is met. Therefor i've created an identifier like this:
[self performSegueWithIdentifier:#"LoginSuccessful1" sender:self];
But it seems like that the segue is beeing pushed even though the performSegueWithIdentifier has not been called. How can i fix this?
Instead of having the segue go directly from the button, hook up the UIButton to an IBAction on your view controller. Then, in this IBAction, check your condition and call your performSegue. When hooking a segue directly from a button, the ViewController is not consulted first.
Remove the segue from button and connect it from ViewController to AnotherViewController as pushsegue not from UIButton
And check the condition and perform the segue
if(this == that){
[self performSegueWithIdentifier:#"LoginSuccessful1" sender:self];
}

Unwinding a segue on interface builder

I am not a regular user of interface builder but I have to use it for this project.
I am using storyboards and have managed to create a push segue from one view controller to another. Lets call it from A to B. Now I am trying to unwind that segue and return from B to A.
I do not need any information to be passed between controllers. I just need to go back.
I have a UIBarButtonItem navigation bar of B, and I have followed this tutorial.
I did this:
A) Created this method on B
- (IBAction)backToViewControllerOne:(UIStoryboardSegue *)segue {
NSLog(#"from segue id: %#", segue.identifier);
}
B) The tutorial author says: "Link this method to a button in the view that you want to unwind from." but this cannot be done because the method has this parameter :(UIStoryboardSegue *)segue and Xcode will not allow a button to be linked to this method, because it expects the method to have this parameter :(id)sender. If I change the parameter to that, the triggered segue cannot be linked.
C) link the button to the green exit on storyboard, choosing the method on 1. OK, did that.
Did all this and clicking on the button does nothing. Not even the action is triggered.
How do I solve that puzzle?
A method with with a particular signature, an IBAction with an argument of type UIStoryboardSegue, needs to be in the controller that you are unwinding to. That (and any other methods like it that you might have in other controllers) will show up in the exit button (of the controller you're unwinding from) when you control-drag to it.

Return values to presenting view controller when navigation back button pressed

I'm having trouble piecing this all together. I have a view controller that opens up another (pushes it on to the navigation stack). On that presented view controller, the user enters a value in a text view. When the user pushes the back button in the navigation, I want to be able to pass the value that they entered in the text view back to the presenting controller.
I've looked for a way to use unwind segue with the back button but haven't found anything. When I create my back button (programmatically) I use initWithTitle:style:target:action but I'm not sure how in implementing the action method that I'll be able to access the value set in the presented controller. Might have to use a delegate to link the two, but not sure of the exact integration point for this scenario.
I feel like I'm so close here and a little help would get me there. Thanks!
The two most common models to use for this interaction are for the child view controller to have either a delegate or a completion block. Either would be set in the prepareForSegue method. My personal preference is the completion block method just because it keeps code contained, but ymmv.
There are also multiple models for detecting when your child view controller is dismissed and you need to invoke the delegate and/or completion:
Use a custom back button. Not a fan of this as it can be an issue to create a back button that really looks and acts like the Apple original, especially if supporting iOS 6 and iOS 7.
Hook viewDidDisappear and see if you're still in the navigation controller's viewControllers array. This is better as the back button works right, but it still feels kind of hokey.
Use the UINavigationBarDelegate method navigationBar:shouldPopItem: This is attractive, especially if you have other validation that needs to happen like checking for saved/unsaved values. To implement this you'll have to subclass UINavigationController and forward the method to your child view controller.
EDIT: Details on Option 2:
-(void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
if(![self.navigationController.viewControllers containsObject:self])
{
// We're not still in the navigation stack so we must've been
// popped. If we were pushed, viewDidDisappear would be called
// but viewControllers containsObject:self would be true
}
}
EDIT: Clarified Option 3: in your navigation controller subclass
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item
{
UIViewController* top = self.topViewController;
if([top respondsToSelector:#selector(navigationBar:shouldPopItem:)])
return [(id)top navigationBar:navigationBar shouldPopItem:item];
return [super navigationBar:navigationBar shouldPopItem:item];
}
Then you can implement navigationBar:shouldPopItem: in the classes that need the functionality.
the back button does not actually comes up with any event associated with itself so that you can pass the values between the previous and to be Popped ViewController.
You would have to implement Delegate pattern to pass values. In this case as you cant catch when backButton is pressed, you need to use custom leftBarButtonItem or use a image with < in itself.

performing programmatic segue after unwind

I have two View Controllers, V1 and V2. V1 presents V2 with a modal segue in storyboard. I then have an unwind segue that dismisses V2 to go to V1. The Done action belowed is then called. It prints out the correct NSLog's, however the [self performSegueWithIdentifier:#"viewmessagessegue" sender:self] does not get initiated. I use that same line of code when a button in V1 is pressed, and it works correctly in that scenario. I'm confused as to why it's not being called...
- (IBAction)done:(UIStoryboardSegue *)segue {
SendToViewController *cc = [segue sourceViewController];
_users = cc.recipients;
NSLog(#"users: %#",_users"); // has the correct data
[self performSegueWithIdentifier:#"viewmessagessegue" sender:self];
NSLog(#"perform segue...:"); // this gets printed
}
I'm not positive of the answer on this one, but I think when "done:" is executed, it's really not executed in that class (meaning the class of the previous screen). It's either executing in an in-between place or in the calling class (meaning the class of the screen you are trying to unwind from). If this is true, it will try to execute prepareForSegue: from the unwind side rather than from V1 side and since there probably isn't a segue.identifier, it just continues on as if it didn't find one....Please don't take this as gospel...I'm guessing here, but I may not be too far off the mark. You could probably put a NSLog in the V2 prepareForSegue: to check it.

Fire a segue conditionally to multiple View Controllers

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)
}
}

Resources