Unwind segue doesn't work sometimes - ios

I have a bunch of views inside of a Nav controller.
The view hierarchy is LoginView -> NAV -> View 1 -> (present a VC. then dismiss) -> push V2 -> push V3 etc
At any view (V1, V2, V3 etc) there is a button that pushes the "Settings View". Inside the settings view there is a logout button.
In SettingsVC.m:
- (IBAction)logout:(id)sender {
NSLog(#"logging out");
// do things here
[self performSegueWithIdentifier:#"UnwindToLogin" sender:self];
}
In LoginVC.m:
- (IBAction)unwindToLogin:(UIStoryboardSegue *)unwindSegue {
self.emailField.text = nil;
self.passwordField.text = nil;
self.loadingSpinner.hidden = YES;
self.failureLabel.hidden = YES;
self.loginButton.userInteractionEnabled = YES;
NSLog(#"unwinding?");
NSLog(#"I hate unwinding");
}
I have hooked up the button in SettingsVC to logout:sender:. When I press the button it logs "logging out" and does all the things inside of the method, but it doesnt unwind to the LoginVC.
[Note: It is often the case that the Application launched and never loaded the Login View. (If there are stored credentials, it skips that view and logs in automatically. But even when the Login View is presented on startup, the unwind sometimes doesnt work.]
[Note 2: Often (but not always), if the last method I edit before running the app is unwindToLogin: the unwind works]
Please help

It seems that sometimes the Login Controller wasn't loaded at unwind time and therefore couldnt be unwinded back. Instead, I unwind to the landing VC which comes before the login, and immediately segue to the login VC

Related

Segue to VC over TabBar

As shown in the photo below, I have a TabBar controller as my root view controller with some navigation controller attached to it. In my VC1 and VC2, I have the tab bar and navigation bar on screen which is what I want, However, for VC3, I do not want tab bar. Currenty I am just hiding and unhiding the tabbar at view will appear. However, this presented some poor user interface. What I think would be great is when I present VC3, it just present "over" the current screen whilst keeping the navigation bar. Is it possible to do that? I'd also like the presentation to be from right to left just like a segue (As oppose to show from bottom)
Note. I use performSegueWithIdentifier for going to VC3
I have seen some solution where I have to set the rootVC to be a normal view controller instead (A login VC in my case) . However, I am trying to avoid that because If the user has already logged in, I do not need to present the login. If I have that as my rootVC all the time, the user will be forced to load and "see" the login VC first before seeing the tabBarVC. This will cause. Unless there is a way to get away with it?
FIRST WAY : PUSH
You can hide bottom bar on push by enabling the flag of VC3 from storyboard.
Please refer following picture:
SECOND WAY : PRESENT
Set a navigation controller for VC3 and present that navigation controller from VC1 or VC2 or TabBar as shown in following image :
You can create a new window, then present your VC3 on it, this would ensure it's over tabbar, in exchange of making new navigation controller and fake back button. But I'd rather check the hide tabbar on push option in storyboard, it give the tabbar hiding a nice animation
For your second question, the best way is make the rootVC in storyboard the tabbar controller, and separate the loginVC, in AppDelegate, check for user login or not and set the rootVC to loginVC or tabbar controller
When first time you login, you have to set the root view controller as login view controller.Then in viedDidLoad method you have to set check already logged in or not.When first run your app it has not logged in so you can go login page.Once you logged in you can go to next page directly.You can use LoginViewController viewDidLoad method.
- (void)viewDidLoad
{
[super viewDidLoad];
BOOL loggedIn = [[NSUserDefaults standardUserDefaults] boolForKey:#"logged_in"];
if(loggedIn)
{
NSLog(#"It has already logged in so go to next view");
}
}
When you login successfully set the bool to YES
- (IBAction)actionLoggin:(id)sender
{
if ([strUsername isEqualToString:#"xxxxxx"] && [strPassword isEqualToString:#"xxxxxx"]) //If it is correct
{
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"logged_in"];
}
}
Then when you log out,set the bool to NO
- (IBAction)actionLoggin:(id)sender
{
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:#"logged_in"];
}
1>For tab bar you can delete Tabbar or you can Hide Tabbar from storyboard but don't take separate nav controller for VC3.
You can kept navbar item title value of VC3 as VC2 or VC1 navbar title while pushing or presenting VC3.For that you have to user performseguewithidentifier and initialise VC3 nabber item title value
2> Hey for user is already login then in appdelgate you can change root view controller.By finding user is logged in or not from user default.
you can change rootviewcontroller using window.rootviewcontroller and while doing log out you again can change rootviewcontroller.

iOS unwind to view not loaded

I'm in front of a little problem :
I have something like that when launching the app :
(1) Splash view --> I am logged ? --> (3) HomeView | else (2) Login view
So when I log, then I go to the home view. And when I logout, I can unwind to the login view because i came from it.
BUT if I don't pass from the login view and redirect directly to the home view, I can't unwind to login view when logout.
Someone know a solution about this ?
I just put my logic here:
Take/add one viewController such like, DummyViewController as rootViewController of your app.
in the DummyViewController's viewWillAppear method put logic like a
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if(login == YES)
{
//go to home screen
}
else
{
// go to login screen
}
}
In DummyViewController you just need to write code in viewWillAppear not anymore.
If you want to Logout then just call
[self.navigationController popToRootViewControllerAnimated:YES];
And viewWillAppear method of DummyViewController will manage your screen based on login status.
You can use NSNotificationCenter to notify your root class when logout done. Then pop to your rootViewController
If you are using UINavigationController, Just present your login controller on NavigationController's RootViewController as like below
-(void)logoutNotification
{
logout = YES;
}
-(void)viewDidAppear:(BOOL)animated
{
if (logout)
{
AuthController * auth = [[AuthController alloc] init];
[self presentViewController:auth animated:NO completion:^{
}];
logout = NO;
auth = nil;
}
}
Maybe is a bit dummy way to do it, but you can simply ALWAYS load the login view and delegate to it the "I'm logged?" check. You can load it hidden or with a waiting sign or whatever... This way you have it already loaded when you logout.
Without seeing your code I can't show you how to, but I guess the logic is enough.

Cocoa-Touch Back Button with Tabbed Navigation

I am developing an iOS application that uses a left side slide out drawer containing tabs, each representing one of the main views of the app. Currently, when the user selects a tab the application searches through the navigation stack for an instance of the relevant view controller and if it finds one pops back to that controller, otherwise it creates a new instance and pushes it onto the stack.
I would like to also add a back button allowing the user to go back to the previous view, however since many navigation options will pop the user to a previous view controller resulting in the controller they are leaving being dealloc'ed there is no obvious way to have a back button to get back to that controller again.
Is there any way to structure this application so that a back button can be added, while still allowing the user to use the tabs to navigate to any view at a given time?
An example of the navigation code follows (invoked when a user clicks one of the tabs):
if(![self.navigation.topViewController isKindOfClass:[GraphViewController class]]) { //Are we already in this view?
BOOL foundController = NO;
for(id controller in self.navigation.viewControllers) { //Is there a controller of this type already in the stack?
if([controller isKindOfClass:[GraphViewController class]]) {
[self.navigation popToViewController:controller animated:YES];
foundController = YES;
break;
}
}
if(!foundController) {
GraphViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"graphViewController"];
controller.connection = _connection;
controller.data = _dataCache;
[self.navigation pushViewController:controller animated:YES];
}
}
I believe what you want is navigation controllers for each item in the slide out menu. This way when the user selects a view from the side menu they can navigate the views associated with that section. This will allow the user to go back from a view once they have selected a item from the side menu.

how to custom back button in storyboard

I create one application that has 3 ViewController with names : (ViewController,ViewController2,ViewController3)
in ViewController exist one button that is for checking file in document folder or download it.for example first to check file if file exist in document folder go to ViewController3 else go to ViewController2 and download it.
so ViewController2 is for download and has UILable & UIProgress for show download status.if file dont exist in this page downloaded and go to ViewController3.
so ViewController3 is for show file.
(these pages connect together with push segue like my image in bottom)
when I go to any page and I click back button return pervious page right??
I when to click on button in first page and file dont exist and downloading in second page then finished download go to page 3.Now I want when to click on back button in page 3 go to page 1 no page 2!!!!
I to work in storyboard.
You can achieve this by intercepting the back button event in ViewController3 and using unwindSegues.
Check out William Jockusch's reply to this question for how to intercept the back button event.
To use the unwind segues in this particular case you then need to:
1) In your ViewController1 create a method such as
- (IBAction)unwindToThisViewController:(UIStoryboardSegue *)unwindSegue
{
NSLog(#"Rolled back");
}
2) In your storyboard zoom out, then ctrl-drag from your ViewController3 to the "Exit" green box on the left contained in your ViewController3 scene (along with the red box First Responder and all your controller view's subviews). A popup window will show, asking what IBAction you want to connect the unwind segue to, and you should be able to select the unwindToThisViewController action that you just created. This will create an unwind segue.
3) Select from the scene box this unwind segue and give it an ID, such as "unwindToStart"
4) Finally, in your ViewController3 class, override the method viewWillDisappear as follows:
-(void) viewWillDisappear:(BOOL)animated
{
if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound)
[self performSegueWithIdentifier:#"unwindToStart" sender:self];
[super viewWillDisappear:animated];
}
This will intercept the back button event and unroll to your ViewController1, and you're good to go.
EDIT: As unwind segues are supported only on iOS 6 and later, if you need this functionality on earlier versions of iOS I think the only way is to manually remove the ViewController2 from the NavigationController stack in your ViewController3's viewDidLoad. Something like the following code should do:
- (void)viewDidLoad
{
NSMutableArray *viewControllers = [NSMutableArray arrayWithArray:self.navigationController.viewControllers];
for(UIViewController* vc in self.navigationController.viewControllers)
{
if ([vc isKindOfClass:[ViewController2 class]]) {
[viewControllers removeObject:vc];
break;
}
}
self.navigationController.viewControllers = [NSArray arrayWithArray:viewControllers];
// Do any additional setup after loading the view.
}

Trouble coordinating view controllers

I have the following view controllers built in my app. The presenting controller is "PatientSelectViewController" (let's call it controller A) and it allows to either manually enter the patient ID in a text field or press a "Scan Barcode" button which would perform a segue to another view controller - namely, "BarcodeScanViewController" (let's call it controller B).
When B finishes scanning a barcode and returns a result (a patient ID), I notify the presenting view controller (A) about it and A is responsible for looking up the ID in a database. At this point the controller B should be dismissed. If the ID is found, then we transition to the third view controller - "PatientConfirmViewController" (let's call it C). However, if the ID is not found then I want a pop up message that says so and go again to the controller B to scan the barcode again.
Similarly, if the user decided to manually enter the ID in the text field instead of scanning it, then a successful ID would take me to the controller C while an unsuccessful one would give a pop up message and remain in controller A for another try.
I also want the controllers to be embedded in a navigation controller, so that I always have tabbar buttons that take me back to the previous view controller. For example, I will have a tabbar button to return to A from either B or C. Ideally, if I reach C after a successful barcode scan, I'd like the tabbar button to take me back to B - not A! - in case the user decides that she doesn't want to confirm this ID, the idea being that the user would likely want to rescan a barcode. But this is not critical.
I am having trouble implementing this for some reason. Here is an example of a screwed up behavior: I am calling A then calling B (to scan a barcode) and scan a barcode that I know is in the database. This correctly brings me to C with the patient info displayed. But then I decide to go back to A using the tabbar button "Enter Patient ID" Then I press the "Scan barcode" button again, again scan the same barcode as before but this time instead of a successful transition to C, I am getting this screen - note the screwed up tabbar! It must be saying "Confirm ID" and "Enter Patient ID" at the same time and the buttons go back to Login (this is the controller that invoked A in the first place) and "Scan Barcode" - that is, the controller B as if it were never popped up previously!
This can happen randomly after 2 or 3 or more successful scans. The log displays this:
nested push animation can result in corrupted navigation bar
Unbalanced calls to begin/end appearance transitions for
.
Finishing up a navigation
transition in an unexpected state. Navigation Bar subview tree might
get corrupted.
Here is how I implemented it:
In view controller A:
-(void)prepareForSegue: (UIStoryboardSegue *)segue sender: (id)sender
{
if ([[segue identifier] isEqualToString:#"BarcodeScanView"])
{
self.p_usingBarcodeScan=YES;
[[segue destinationViewController]setViewDelegate:self]; //sets itself as a delegate for receiving the result of a barcode scan from controller B
}
if ([[segue identifier] isEqualToString:#"ConfirmID"])
{
[[segue destinationViewController] setP_userInfo:p_userInfo] ; //passes the data to the controller C
}
}
The delegate method for receiving a barcode scan result (still in controller A):
- (void) didScanBarcode:(NSString *)result
{
self.p_userID = result;
[self.navigationController popViewControllerAnimated:YES];//Pop B from the navigation stack to return to A - is this right????
//Run the database query
[self lookUpID];
}
The method that looks up the ID in the database (still in A):
- (void) lookUpID{
/*.....
Does something here and gets a result of the lookup...
*/
// Do something with the result
if ([[result p_userName] length] > 0 ){ //Found the user!
p_userInfo = result;
[self performSegueWithIdentifier: #"ConfirmID" sender: self];
}
else {
UIAlertView * messageDlg = [[UIAlertView alloc] initWithTitle:nil message:#"User was not found. Please try again"
delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[messageDlg show];
//Here I'd like to perform this seque to B only if I got here after a barcode scan...
//Otherwise I am just staying in A...
if (self.p_usingBarcodeScan == YES ){
[self performSegueWithIdentifier: #"BarcodeScanView" sender: self];
}
}
return;
}
Just for completeness, in B once I managed to scan a barcode, I am calling this:
- (void)decodeResultNotification: (NSNotification *)notification {
if ([notification.object isKindOfClass:[DecoderResult class]])
{
DecoderResult *obj = (DecoderResult*)notification.object;
if (obj.succeeded)
{
decodeResult = [[NSString alloc] initWithString:obj.result];
[[self viewDelegate] didScanBarcode:decodeResult];
}
}
}
I am using push seques from A to B and from A to C and using storyboards.
Here is a snapshot of the storyboard, with the segues from A to B ("BarcodeScan") and A to C ("ConfirmID") visible. Both are push segues:
Thanks a lot in advance!
You don't say whether you are currently using a navigation controller and push segues, or presenting with modal segues.
Here:
[self.navigationController popViewControllerAnimated:YES];//Pop B from the navigation stack to return to A - is this right????
[self dismissViewControllerAnimated:YES completion:nil];**//is this right???**
The first is correct for returning from a push segue, the second is appropriate for a modal/presenting segue. The push-return method is effectively what happens when you use the back button in a navigation controller.
update
I think you need to untangle your navigation methods a little. What I suggest
In B, have a delegate method that
checks the patient ID
if it's good sets self.p_userID in A
returns a BOOL success/fail back to B.
_
based on that result, either :
pop yourself off (you can use [self.navigationController popViewController] directly in B) or
bring up your alert in B. Given that you have a back button in B, and (perhaps) a rescan button, your alert may not need to present any choices.
In A :
- (void) viewWillAppear:(BOOL)animated
{
NSLog (#"viewControllers %#",self.navigationController.viewControllers);
[super viewWillAppear:animated];
if (self.p_userID) {
[self performSegueWithIdentifier: #"ConfirmID" sender: self];
self.p_userID = nil;
}
}
(this performSegue should only happen if you set self.p_userID while you were still in B)
The typed-in userID logic is simpler. Again you check the patient id. If it is not there, throw up an alert in A (again, you shouldn't need to present choices, as all the nav options are available without the alert). If it is there, set self.p_userID to the ID and initiate the segue.
In prepareForSegue you should do your lookup to get the userInfo dictionary from self.p_userID to pass to C, then set self.p_userID to nil. Alternatively (better) just pass self.p_userID to C and do the lookup in C (assumes you have a separate model source object). Whatever you do, be sure to set self.p_userID to nil whenever you leave A so that you don't auto-trigger a segue you don't want! Perhaps zero it in 'viewWillDisappear' as well.
OK, I am trying to partially answer my own question.
Even after implementing He Was suggestion above, my troubles persisted and even multiplied (some details on these are in my comment in the discussion thread https://chat.stackoverflow.com/rooms/23918/discussion-between-peterd-and-he-was)
However, by some change I googled the log message I was getting: "nested push animation can result in corrupted navigation bar" and wound up reading this answer: https://stackoverflow.com/a/5616935/1959008, which suggested that my issue was using
[self.navigationController popViewControllerAnimated:YES];
that is with animated set to YES. Once I set it to NO, the issues with the tabbar disappeared (some small quirks remain and I hope to solve them soon). This is really strange - looks more like a bug than a feature to me, but I could be wrong of course...

Resources