How should I present a Modal VIew Controller from TabBarController - ios

I have an app whose initial scene is a tab bar controller with 3 tabs. I created a uitabbarcontroller class and set it to that scene (MainTabViewController).
In that class I call presentLogin from the viewDidAppear method and that method reads:
- (void)presentLogin{
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
if (![prefs stringForKey:#"storedUser"] && ![prefs stringForKey:#"storedPass"]) {
NSLog(#"No user prefs stored");
// BUT WAIT, before all this, lets pop up a view controller for user registration
UIStoryboard* sb = [UIStoryboard storyboardWithName:#"Storyboard" bundle:nil];
ModalViewController *popupController = [sb instantiateViewControllerWithIdentifier:#"ModalViewController"];
[self presentViewController:popupController animated:YES completion:nil];
} else {
NSString *storedUser = [NSString stringWithFormat:#"User:%#",[prefs stringForKey:#"storedUser"]];
NSString *storedPass = [NSString stringWithFormat:#"User:%#",[prefs stringForKey:#"storedPass"]];
UIAlertView *internetAlert = [[UIAlertView alloc] initWithTitle:storedUser
message:storedPass
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Ok", nil];
[internetAlert show];
}
}
But the modalVC isnt showing for some reason. I get this crash log:
Attempting to begin a modal transition from <MainTabViewController: 0xa55d0d0> to <ModalViewController: 0x15e2b5e0> while a transition is already in progress. Wait for viewDidAppear/viewDidDisappear to know the current transition has completed

I believe you get this error because the tab bar controller is putting the view of the controller in its first tab on screen at the same time you're presenting the modal controller. Instead of presenting it from the tab bar controller, present it in the viewDidAppear method of the controller in the first tab. Call it with no animation to see the modal view controller without seeing the firs tab controller.

Try to add a tiny delay like below:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self performSelector:#selector(presentLogin) withObject:nil afterDelay:0.1];
}

The view of the tabbarcontroller contains the viewHierarchies of the viewControllers that the tab bar itself owns. Maybe something is of because of that. Try to see of you still get the error if you only have one viewcontroller set to the tabbar.

Related

(UINavigationController) PushViewController fails after PresentViewController

I am building an application in which the main content is a navigation controller. However, the first time the app loads, I am pushing another view controller on top of that controller (the user has to register etc). This has to be separate from the navigation controller so the user cannot just backtrack. Currently, Navigation through my navigation controller works only on times when the other view controller isn't loaded.
The code to load the 'register' VC if it is needed:
NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
if ([prefs objectForKey:#"id"]) {
}
else {
RegisterViewController * rvc = [storyboard instantiateViewControllerWithIdentifier:#"registerVC"];
[self.navigationController presentViewController:rvc animated:NO completion:^(void){}];
}
On completion of registration:
[viewCtrl dismissViewControllerAnimated:NO completion:^(void){
NSLog(#"VIEW DISMISSED PROPERLY");
}];
Note: viewCtrl is the viewController pushed on. Also On times when the navigationcontroller stops working 'view dismissed properly' is called. So I know that the view is being dismissed.
Finally, code to push another page normally onto the navigation controller:
ContactViewController * cvc = [self.view.storyboard instantiateViewControllerWithIdentifier:#"contactVC"];
[self.view.navigationController pushViewController:cvc animated:NO];
Just to reiterate, ContactViewController only loads if the registration page wasn't loaded.
Thanks in advance.
You should push other ViewController after dismiss finished:
[viewCtrl dismissViewControllerAnimated:NO completion:^(void){
NSLog(#"VIEW DISMISSED PROPERLY");
ContactViewController * cvc = [self.view.storyboard instantiateViewControllerWithIdentifier:#"contactVC"];
[self.view.navigationController pushViewController:cvc animated:NO]
}];

Difficulties changing between viewControllers in xcode

I have 3 different viewControllers. first one is the main page where you will see many thumbnail of images. Second controller will only open when an image is clicked. Last ViewController is the login which opens when the user is not logged in but trying to view the second controller or clicked login from the main controller. In the login controller I have a back button to go to main page. Remember the user can access login from two different ways. by clicking on the image or clicking login in the main controller directly. So the issue is the back button does work properly. When the login page is accessed from the main controller directly, then the back button works fine it takes you to the main page. However, if you click on any of the thumbnails the second controller will try to open but since your not logged in the login page will show and when you click the back button to send you to the main controller, it won't go to main instead it will reload the same page and you kind of stock in a loop you have to login or exit the app. any ideas why?
The First ViewController Identifier is set to "mainImages".
- (IBAction)backToMain:(id)sender {
[self dismissViewControllerAnimated:NO completion:nil];
NSString * storyboardName = #"MainStoryboard";
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle: nil];
UIViewController * MI = [storyboard instantiateViewControllerWithIdentifier:#"mainImages"];
UINavigationController *navController = self.navigationController;
if (navController) {
[navController pushViewController:MI animated:NO];
}
}
I also tried this but no luck:
[self dismissViewControllerAnimated:NO completion:nil];
UIViewController * MI = [self.storyboard instantiateViewControllerWithIdentifier:#"mainImages"];
[self presentViewController:MI animated:NO completion:nil];
Try this if you are presenting as modal:
- (IBAction)backToMain:(id)sender {
[self dismissViewControllerAnimated:NO completion:^{
UIWindow *window = [[UIApplication sharedApplication].windows firstObject];
UINavigationController *nav = (UINavigationController *)window.rootViewController;
[nav popToRootViewControllerAnimated:YES];
}];
}
If you need to "go back" from the login view to any other view (in this case 2 of them), you should try using a Modal Segue, a modal segue lose the navigation controller view stack but allow you to dismiss the login view and go back to you last view.
On the first (or thumbnail images view controller) show your new modal view as this:
UIViewController *modalViewController = [[UIViewController alloc] init];
[self presentViewController:modalViewController animated:YES completion:nil];
And then, to dismiss (on login view controller):
- (IBAction)backToMain:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}

not able to move to another view controller

I'm trying to move from 1st view controller -> 2nd view controller. However, it seems nothing is happening.
It is mainly from a sign-in page, so I don't need to get back to that page once I have a successful login. I'm trying right now from a button just to check.
I have 2 classes.
signInPage
optionsScreen
I need to go from 1 > 2
Inside signInPage.m
#import "optionsScreen.h"
- (IBAction)moveToNext:(id)sender {
optionsScreen *aSecondPageController =
[[optionsScreen alloc]
initWithNibName:#"optionsScreen"
bundle:nil];
[self.navigationController pushViewController:aSecondPageController animated:YES];
// [aSecondPageController release];
}
Screenshot of story board
https://drive.google.com/file/d/0B1y7ao4cGAYKaU0yRlVxcVg2bzA/edit?usp=sharing
Solution:
- (IBAction)moveToNext:(id)sender {
NSString * storyboardName = #"Main_iPhone";
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:storyboardName bundle: nil];
UIViewController * vc = [storyboard instantiateViewControllerWithIdentifier:#"optionsScreen"];
[self presentViewController:vc animated:YES completion:nil];
}
In storyboard add navigation view controller. As root view controller - perhaps by default it is table view controller, so remove it - set up 1st view controller. Adding navigation controller you can push to navigation stack as many view controllers as you want.
I think what you're trying to do is present the view controller modally, not push it onto the stack (which doesn't exist). Instead you should just do this. This will not show a back button by default so you'll have to add one manually:
optionsScreen *aSecondPageController = [[optionsScreen alloc] initWithNibName:#"optionsScreen" bundle:nil];
[self presentViewController:aSecondPageController animated:YES completion:nil];
To go back, in your optionsScreen view controller, you'll need some sort of back button right? So you can do this in your viewDidLoad:
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStyleBordered target:self action:#selector(backPressed:);
Then you'll have to create a new method called backPressed:
-(void)backPressed:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
Edit: it turns out you were trying to do what I explained above. I will leave the navigation controller bit for future reference.
If you are trying to use a navigation controller to push your options, which will give you a back button by default, then first, in your storyboard, click your sign in view controller, then at the menu bar at the top go to Editor > Embed In... > Navigation Controller. Then in your view controller switch method, you can do what you were trying before
optionsScreen *aSecondPageController = [[optionsScreen alloc] initWithNibName:#"optionsScreen" bundle:nil];
[self.navigationController pushViewController:aSecondPageController animated:YES];

Push to ViewController without back button

I am developing an iOS app which contains login/authentication functionality - basically first time a user logins, in they need to enter relevant login details - then they are passed to main app screens - subsequent visits to the app they will be automatically authenticated.
All above works fine - the issue I have is with the Navigation bar - it appears in the main screen in the main part of the app with a back button - I don't want this to be displayed as they should not be able to return to the login screen once authenticated. I guess it's using the root navigation controller which explains the logic, but is there a way to ignore the navigation controller of the login section so the back button is not displayed in the main app.
Below is a screenshot of the structure to help my explanation - left hand group of screens are the login process right hand is the main app structure.
The code used to switch screens is as follows -
SWRevealViewController *swRevealController = (SWRevealViewController *)navVC;
swRevealController.managedObjectContext = self.managedObjectContext;
[self.navigationController pushViewController:controller animated:YES];
Don't push view controller. Create new hierarchy with:
Objective-C
[self.navigationController setViewControllers:#[controller] animated:YES];
Swift
navigationController.setViewControllers([controller], animated:true)
In the Screen which implements after login, the ViewDidLoad method add the line to hide back bar button.
self.navigationItem.hidesBackButton = YES;
Additionally you can add an 'Logout' option as
UIBarButtonItem *backBarButton = [[UIBarButtonItem alloc] initWithTitle:#"Logout" style:UIBarButtonItemStyleBordered
target:self
action:#selector(LogoutClick)];
self.navigationItem.leftBarButtonItem = backBarButton;
-(void)LogoutClick {
[self showLogoutAlert];
}
-(void)showLogoutAlert {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#""
message:#"Do you want to Logout ?"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"Logout", nil];
[alert show];
}
- (void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
if (buttonIndex == 1) {
[self.navigationController popToRootViewControllerAnimated:YES];
}
}
Use a modal view controller for the login screen so its not part of your navigation controller hierarchy within your navigation root probably in view did load - sample code below:
- (void) viewDidLoad {
......
LoginVC *loginViewController = [LoginVC alloc] init];
loginViewController.parent = self;
[self presentViewController: loginViewController animated:YES completion:NULL];
return;
}
You may or may not dismiss the modal view from the root - if you do, you will need to be able to call the dismiss routine from loginViewController but there are other ways as well and you can put the dismiss inside the modal view (loginViewController)
(void) login_exit:(BOOL)success {
[self dismissViewControllerAnimated:YES completion:NULL];
if !(success) {
.... send message (UIAlertview and ask if he or she wants to try again)
}
else {
}
return;}
Very simple.. Just navigate to rootviewcontroller
SWRevealViewController *swRevealController = (SWRevealViewController *)navVC;
swRevealController.managedObjectContext = self.managedObjectContext;
//-- I think above lines not needed as per your question
[self.navigationController popToRootViewControllerAnimated:YES];
You need to hide the Navigation Bar in the ViewController before you push the SWRevealViewController by using the below line in viewDidLoad
Objective C
self.navigationController.navigationBarHidden = true;
Swift
self.navigationController?.navigationBarHidden = true;
It will not show the back button in the next view controller pushed
2022 answer
#IBAction func SomeScreen() {
let s = UIStoryboard ...
navigationController?.isNavigationBarHidden = true;
navigationController?.pushViewController(s, animated: true)
}
It's that easy.
Write this in viewDidLoad method
self.navigationItem.hidesBackButton = YES;
This will hide the back button for the first view.
ClassA* controller = [storyboard instantiateViewControllerWithIdentifier:#"ClassAIdentifier"];
if (controller != nil) {
[self.navigationController pushViewController:controller animated:YES];
[controller.navigationItem setHidesBackButton:YES animated:NO];
}
You MUST hide the navigationItem after push, otherwise it will be nil.

Dismiss a nib an pop to a view controller in a tab bar controller

My app it's embedded in a tab bar controller, but I'm using a nib in which I'm doing some operation.
At the end of this operation I've to pop to a view controller in my tab bar controller. I tried to use this code (suggested by an another user on here):
[self.tabBarController setSelectedIndex:<#(NSUInteger)#>];
[self.navigationController popToRootViewControllerAnimated:NO];
but it doesn't do anything. I guessed i should use this instructions:
[self dismissViewControllerAnimated:NO completion:nil];
but I've to put something in the completion, I don't know how to do that, can you help me?
Update:
I will show you my storyboard so you can understand what I'm trying to explain:
also in "Product View Controller", when I press on a button (I design the button by code) it shows me this file XIB:
In this view there's a button (usually it's hidden), when I press on this button it will add the product I'm looking to a remote cart. After the adding this product to the remote cart, I will pop to the "Carriage View Controller". When I pop to the "Carriage View Controller" I should pass them some data of my cart.
So my app has this tab view:
Home View Controller: the main view controller, loaded when the app is started
Category View Controller: it's a table view controller in which I'm displaying the category of products in my store
Carriage View Controller: a controller in which I will display the data of my cart
Info View Controller: a controller in which I display information about the app
From the "Category View Controller" with a segue I pop a View Controller in which I display by a custom view the products. When I press on one of this products it calls a the xib file I posted before file in which there are the details of the product selected before.
I hope you understand better what I'm trying to do.
CODE:
Here's the code to display the xib I posted:
- (void)collectionView:(PSCollectionView *)collectionView didSelectCell:(PSCollectionViewCell *)cell atIndex:(NSInteger)index {
int productID = (int)index;
productID = productID + 1;
if (IS_IPHONE_5) {
ProductScannedViewController *productScannedVC = [[ProductScannedViewController alloc]initWithNibName:#"ProductScannedViewControllerRetina4" bundle:nil];
productScannedVC.idProductScanned = [NSString stringWithFormat:#"%d", productID];
[productScannedVC setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[self presentViewController:productScannedVC animated:YES completion:nil];
} else {
ProductScannedViewController *productScannedVC = [[ProductScannedViewController alloc]initWithNibName:#"ProductScannedViewController" bundle:nil];
productScannedVC.idProductScanned = [NSString stringWithFormat:#"%d", productID];
[productScannedVC setModalTransitionStyle:UIModalTransitionStyleFlipHorizontal];
[self presentViewController:productScannedVC animated:YES completion:nil];
}
}
[self dismissViewControllerAnimated:YES completion:^{
// completion code here
}];
You can only use the popToRootViewControllerAnimated method if the view controller is part of that navigation controller's navigation stack.
If you are presenting the view controller like this
TestViewController * vc = [[TestViewController alloc] init];
[self presentViewController:vc animated:YES completion:^{
}];
And want to do the tab navigation inside I suggest injecting the tab bar as a weak property
TestViewController * vc = [[TestViewController alloc] init];
[vc setTabController:self.tabBarController];
[self presentViewController:vc animated:YES completion:^{
}];
Inside the TestViewController where you want to do the navigation:
[self dismissViewControllerAnimated:YES completion:^{
[tabController setSelectedIndex:0];
}];
I tested this code.

Resources