performing programmatic segue after unwind - ios

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.

Related

Does an unwind segue release memory use of intermediate VCs, if no navigation controller is used? [duplicate]

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

iOS - unwind to my initial view controller

I am using an unwind segue to unwind to the initial view controller in my storyboard. The unwind works great, I implemented this method in my initial view controller:
- (IBAction) unwindToInitialViewController:(UIStoryboardSegue *) unwindSegue {
}
However if I try an segue to another view controller after I do the unwind I get the following error:
Warning: Attempt to present on
whose view is not in the window
hierarchy!
It seems like this only occurs if I unwind to the view controller that is checked as 'Initial View Controller' in the storyboard. Is this a bug? Should I be able to unwind to that initial controller? Other ideas?
EDIT:
Here is how I perform the second segue:
[self performSegueWithIdentifier:#"mySegue" sender:nil];
I should note that this is a login/logout problem. When I log in the first time the segue from my login controller to my next controller works. When I logout I unwind to the initial view controller. I then log in again and the segue from my login controller to the next controller does not work.
EDIT 2:
From more research I have found its because I am using a delegate for my login. Login is async, I make a call with AFNetworking and when its done I call my login delegate (the login VC in this case). At that point the login VC can segue to the view.
Login code:
- (void) login: (NSDictionary *) parameters {
[http.manager POST:url parameters:parameters success:^(AFHTTPRequestOperation *operation, NSDictionary *response) {
[self.loginDelegate loginSuccess:response];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
[self.loginDelegate loginFailure:error];
}];
}
My login VC which is the delegate:
- (void) loginSuccess:(NSDictionary *) response {
// setup user info based on response
...
// Segue
[self performSegueWithIdentifier:#"loginSuccessSegue" sender:nil];
}
I have checked that I am in the main thread when and I segue and still no luck. I know that AFNetworking always calls the success/failure blocks on the main thread too.
The tricky part. If I change that above code to use a block and not a delegate the storyboard/segue does not get messed up and I can login and logout many times with no problem.
Why does the segue work the first time with the delegate pattern, but on logout (unwind), can I not use that segue again?
EDIT 3:
More investigation shows that on unwind my login VC viewDidAppear is called twice. On initial unwind the view looks to still be on the stack, show it shows quickly and viewDidAppear is called. However this is animated away quickly and viewWillAppear is called a second time with a different VC. I think this might be the root of the problem. Why when I unwind to that VC is it animated away only to be animated back in?
Please check, whether your loginDelegate is nil during the second login attempt. If it is nil the "delegate calls" will just go to nowhere. Also please check whether the loginDelegate points to the instance you expect. If it points to an "old" instance, the wrong views may be tried to be presented.
The set of methods viewDidLoad, viewDidAppear, viewWillAppear, etc. can be called in unexpected order especially when going back in navigation or presenting an ad and coming back from it. If you have different initialization/setup tasks distributed among these methods, you may end up with a partly initialized view controller.
(Thinking about the problem I lost your statement about the error encountered, so probably the delegate is not nil.)
EDIT:
I ran one of my tiny unwind test projects and there log the viewDidAppear calls:
viewDidAppear: <ViewController: 0x7a687700>
viewDidAppear: <VC2: 0x7a70e970>
viewDidAppear: <VC3: 0x7a694d50>
unwind target
viewDidAppear: <VC2: 0x7a70e970>
viewDidAppear: <ViewController: 0x7a687700>
viewDidAppear: <VC2: 0x7a71b790>
viewDidAppear: <VC3: 0x7a694d20>
unwind target
viewDidAppear: <VC2: 0x7a71b790>
viewDidAppear: <ViewController: 0x7a687700>
Doing the unwind in VC3 briefly shows VC2 and eventually ends up at the target ViewController. Now the second "login" leads to different instances of the view controllers.
Do you keep references to "old" view controllers?
Another reason might be, that your "logout" detection fires twice (once when coming along the unwind and one more time when an intermediate or the initial view controller detects the need to login?).

trouble understanding Obj-C MVC: exit and unwindSegue

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

Async call to WebService segue happens before data comes back

The Background
I have an iOS application I am building with StoryBoard for the UI and AFNetworking for the back end. On my UIViewController I have some text boxes. When the user hits a submit UIButton I am making the AFNetworking call to a web service which returns an XML file.
The issue
I used the StoryBoard interface to hook up the button to a UITableViewController. Initially this worked fine because I had the code for the web service call inside the UITableViewController. The problem is that if the web service returns nothing I get a blank UITableView. So I moved the web service call into the UIViewController to be called when the button is pushed. OOPS! I make the web service call and the prepareForSegue gets called before the web service returns the data since its Asynchronous.
My Question
What is the best approach when getting data from a web service when you are using storyboards. Should I make the call inside the UITableViewController and then go back to the UIViewController if there is no data or an error returned from the service. If so how do I get back. I have tried self.navigationController popViewController but it comes up with a warning in the console that removing a view controller can mess up the stack.
Is there a way that I can control when the segue is called and only do it once I have data back from the web service?
Posting this step by step answer to help anyone else out that may need help with this:
Connect the two ViewControllers in Storyboard which will create a segue.
You simply select the first view controller (make sure the viewController is highlighted with a blue line and you didn't select like an image view or navigationItem. Then CTRL and click on the destination view controller (where you want to transition to).
The round circle in the middle is called the segue. If you click on that and go to your attributes panel on the right, you will see this:
In the identifier enter a name you want to call this transition: for example myNewPage.
Whenever you are ready to make the call to the next scene. Use this delegate method to do pass data from one controller to another or do anything special:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NameOfDestinationController *dataViewController;
dataViewController = segue.destinationViewController;
if ([segue.identifier isEqualToString:#"nameOfYourSegueIdentifier"]) {
dataViewController.variable1 = self.variable1;
...
}
}
Then in my case I needed to trigger the segue after I got a success from the AFNetworking web service call. So in the Success block I added this line:
[self performSegueWithIdentifier:#"nameOfYourSegueIdentifier" sender:self];
It works perfectly now!!!

iOS 6 - programmatically triggering Unwind Segue does nothing

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

Resources