I am using storyboards for FIRST time in my iOS app. I have 2 views in my Storyboard (A & B). Lets say A is my initial view controller in my storyboard. When my app launched, I can see view controller A. So far everything is working as per expectation. Now in my view controller A, I am checking whether user is logged in or not. If user is not logged in then I want to present view controller B. How can I show B modally using PresentModalViewController programmatically?
Here is my set up
Here is my code
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
if (!isUserLoggedIn) {
NSLog(#"USER NOT LOGGED IN....");
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
LoginViewController *vc = (LoginViewController*)[mainStoryboard instantiateViewControllerWithIdentifier:#"B"];
[self presentModalViewController:vc animated:YES];
}
}
What you have done so far seems correct.. Did you remember to actually set the identifier of B in the storyboard?
Also, you might want to try
[self.storyboard instantiateViewControllerWithIdentifier:#"B"];
instead of what you're doing.
Update:
Here's what the viewDidLoad method might look like:
- (void)viewDidLoad {
[super viewDidLoad];
if (!isUserLoggedIn) {
NSLog(#"User is not logged in.");
LoginViewController *vc = (LoginViewController *)[self.storyboard instantiateViewControllerWithIdentifier:#"B"];
[self presentModalViewController:vc animated:YES];
}
}
Also, I see from the image that your first view controller isn't set to any particular class. It just says "View Controller", while the second one shows "Login View Controller" correctly.
Note: I don't have access to Xcode right now, so I haven't tested it yet.
Related
I have this design in my storyboard:
As you see the middle ViewController is connected to a Navigation Controller ( I just made the navigation bar invisible). In this middle page I add all the controls programmatically ( using Parse mobile platform login wizard ).
The thing is I want to navigate to the third page, when Login is done successfully.
- (void)logInViewController:(PFLogInViewController *)logInController didLogInUser:(PFUser *)user {
[self dismissModalViewControllerAnimated:YES];
ViewController2 *viewController = [[[ViewController2 alloc] init] autorelease];
[self.navigationController pushViewController:viewController animated:YES];
printf("%s", [#"Ali" UTF8String]);
}
the message will be printed, but the navigation is failed. Can you help? Perhaps I need to fix something in storyboard.
I'm not sure you should be sending the dismissModalViewControllerAnimated: message. Are you presenting another view controller modally on top of your login view controller?
Anyway, when you have configured a view controller in a storyboard, you can't create the view controller using alloc and init. You need to ask the storyboard to create it. There are a couple of ways to do this.
One way
One way to get the storyboard to create your ViewController2 is to make a push segue in your storyboard.
Open your storyboard.
Control-drag from the login view controller to the ViewController2.
Choose the “push” segue type.
Click on the segue.
Choose View > Utilities > Show Attributes Inspector.
In the Attributes Inspector (right-hand side of the window), set the segue identifier to “didLogIn”.
To execute the segue, do this:
- (void)logInViewController:(PFLogInViewController *)logInController didLogInUser:(PFUser *)user {
[self dismissModalViewControllerAnimated:YES];
[self performSegueWithIdentifier:#"didLogIn" sender:self];
}
Another way
Another way to get the storyboard to create your ViewController2 is to give it a storyboard ID and ask the storyboard to instantiate the view controller by ID. Then you can push the view controller.
Before you can ask the storyboard to create it, you must give a “storyboard ID” to the ViewController2 instance in your storyboard.
Open your storyboard.
Select the ViewController2 instance.
Choose View > Utilities > Show Identity Inspector.
In the Identity Inspector (right-hand side of the window), enter “viewController2”. Case is important!
Then, in your code, ask the storyboard to instantiate viewController2:
- (void)logInViewController:(PFLogInViewController *)logInController didLogInUser:(PFUser *)user {
[self dismissModalViewControllerAnimated:YES]; // Should this be here?
ViewController2 *viewController = [[[ViewController2 alloc] init] autorelease];
[self.navigationController pushViewController:viewController animated:YES];
}
Try this way,
ViewController2 *myViewController=[storyboard instantiateViewControllerWithIdentifier:#"TheNameOfYourController"]
[self.navigationController pushViewController:myViewController animated:YES];
Because a lot of relevant code/settings is missing I would suggest the following:
Use this code:
-(void)logInViewController:(PFLogInViewController *)logInController didLogInUser:(PFUser *)user {
[self dismissViewControllerAnimated:YES completion:NULL];
Class klass = [[NSBundle mainBundle] classNamed:#"ViewController2"];
ViewController2 *viewController = [[klass alloc] init];
[self.navigationController pushViewController:viewController animated:YES];
[viewController release];
NSLog("%#", #"Ali");
}
If that doesn't work check how you suppose to initialize ViewController2.
If all this doesn't help please try to post more relevant code.
To move next viewController using Storyboard Identifier
nextViewController *objnextViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"nextViewControllerIdentifier"];
[self.navigationController pushViewController: objnextViewController animated:YES];
I've seen this question in various forms without a clear answer. I'm going to ask and answer here. My app needs to do work on startup... inits, a few network calls, login, etc. I don't want my main view controller working until that's done. What's a good pattern for doing this?
Requirements:
iOS5+, storyboards, ARC.
the main vc's view cannot appear until the startup work is done.
want a choice of transition styles to the main vc when the startup work is done.
want to do as much layout as possible in storyboard.
the code should be cleanly contained somewhere obvious.
EDIT, July 2017
Since the first writing, I've changed my practice to one where I give the start up tasks to their own view controller. In that VC, I check startup conditions, present a "busy" UI if needed, etc. Based on the state at startup, I set the window's root VC.
In addition to solving the OP problem, this approach has the additional benefits of giving better control of the startup UI and UI transitions. Here's how to do it:
In your main storyboard, add a new VC, called LaunchViewController and make it the app's initial vc. Give your app's "real" initial vc an identifier like "AppUI" (identifiers are on the Identity tab in IB).
Identify other vcs that are the starts of main UI flows (e.g. Signup/Login, Tutorial, and so on) and give these descriptive identifiers, too. (Some prefer to keep each flow in it's own storyboard. That's a good practice, IMO).
One other nice optional idea: give your app's launch storyboard's vc an identifier, too (like "LaunchVC"), so that you can grab it and use it's view during startup. This will provide a seamless experience for the user during launch and while you do your startup tasks.
Here's what my LaunchViewController looks like....
#implementation LaunchViewController
- (void)viewDidLoad {
[super viewDidLoad];
// optional, but I really like this:
// cover my view with my launch screen's view for a seamless start
UIStoryboard *storyboard = [self.class storyboardWithKey:#"UILaunchStoryboardName"];
UINavigationController *vc = [storyboard instantiateViewControllerWithIdentifier:#"LaunchVC"];
[self.view addSubview:vc.view];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self hideBusyUI];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self showBusyUI];
// start your startup logic here:
// let's say you need to do a network transaction...
//
[someNetworkCallingObject doSomeNetworkCallCompletion:^(id result, NSError *error) {
if (/* some condition */) [self.class presentUI:#"AppUI"];
else if (/* some condition */) [self.class presentUI:#"LoginUI"];
// etc.
}];
}
#pragma mark - Busy UI
// optional, but maybe you want a spinner or something while getting started
- (void)showBusyUI {
// in one app, I add a spinner on my launch storyboard vc
// give it a tag, and give the logo image a tag, too
// here in animation, I fade out the logo and fade in a spinner
UIImageView *logo = (UIImageView *)[self.view viewWithTag:32];
UIActivityIndicatorView *aiv = (UIActivityIndicatorView *)[self.view viewWithTag:33];
[UIView animateWithDuration:0.5 animations:^{
logo.alpha = 0.0;
aiv.alpha = 1.0;
}];
}
- (void)hideBusyUI {
// an animation that reverses the showBusyUI
}
#pragma mark - Present UI
+ (void)presentUI:(NSString *)identifier {
UIStoryboard *storyboard = [self storyboardWithKey:#"UIMainStoryboardFile"];
UINavigationController *vc = [storyboard instantiateViewControllerWithIdentifier:identifier];
UIWindow *window = [UIApplication sharedApplication].delegate.window;
window.rootViewController = vc;
// another bonus of this approach: any VC transition you like to
// any of the app's main flows
[UIView transitionWithView:window
duration:0.3
options:UIViewAnimationOptionTransitionCrossDissolve
animations:nil
completion:nil];
}
+ (UIStoryboard *)storyboardWithKey:(NSString *)key {
NSBundle *bundle = [NSBundle mainBundle];
NSString *storyboardName = [bundle objectForInfoDictionaryKey:key];
return [UIStoryboard storyboardWithName:storyboardName bundle:bundle];
}
#end
The original answer below, though I prefer my current approach
Let's express the app's readiness to run the main vc with a boolean, something like:
BOOL readyToRun = startupWorkIsDone && userIsLoggedIn;
Create an AppStartupViewController and lay it out in app storyboard.
Don't drag any segue's to it, and don't make it the staring vc, just leave it floating somewhere.
In the vc's attributes inspector in storyboard, set it's identifier as "AppStartupViewController".
In the AppStartupViewController.m, when the readyToRun conditions have been met, it can dismiss itself:
self.modalTransitionStyle = UIModalTransitionStyleCrossDissolve; // your choice here from UIModalTransitionStyle
[self dismissViewControllerAnimated:YES completion:nil];
Now, whenever the app becomes active, it can check for readiness to run, and present the AppStartupViewController if it's needed.
In AppDelegate.h
- (void)applicationDidBecomeActive:(UIApplication *)application {
BOOL readyToRun = startupWorkIsDone && userIsLoggedIn;
if (!readyToRun) {
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
AppStartupViewController *startupVC = [storyboard instantiateViewControllerWithIdentifier:#"AppStartupViewController"];
[self.window.rootViewController presentViewController:startupVC animated:NO completion:nil];
// animate = NO because we don't want to see the mainVC's view
}
}
That's mostly the answer, but there is one hitch. Unfortunately the main vc gets loaded (that's okay) and gets a viewWillAppear: message (not okay) before the AppStartupViewController is presented. It means we have to spread a little extra startup logic, like this, in MainViewController.m:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (readyToRun) {
// the view will appear stuff i would have done unconditionally before
}
}
I hope this is helpful.
Another solution when using a navigation controller.
Your navigation controller being the initial view controller, set your main controller as the navigation controller root's view.
Add your loading controller to the storyboard and link with a named segue of style modal.
In your main controller's viewWillAppear trigger the segue (only once per app run).
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if(_isFirstRun)
{
_isFirstRun = NO;
[self performSegueWithIdentifier:#"segueLoading" sender:nil];
}
}
It will not work if you trigger the segue in viewDidLoad probably because the navigation controller's animation is not done yet and you will receive an unbalanced calls to begin/end appearance transitions
I have only two ViewControllers in my app
Storyboard setting: -
I have a push segue directly connected from my ViewController to MainTableViewController.I want to my app to redirect to Main View as soon as user enters the right passcode.
Here is my code: -
- (IBAction)checkInput
{
NSMutableString *aString = [NSMutableString stringWithFormat:firstDigit.text];
[aString appendFormat:secondDigit.text];
[aString appendFormat:thirdDigit.text];
[aString appendFormat:fourthDigit.text];
if([aString isEqualToString:#"1111"])
{
result.text = #"Right Password";
//UIStoryboard *sb = [UIStoryboard storyboardWithName:#"MainStoryBoard" bundle:nil];
//UITableViewController *tvc = [self.storyboard instantiateViewControllerWithIdentifier:#"mainTableViewController"];
//[self.navigationController pushViewController:tvc animated:YES];
//[self removeFromParentViewController];
[self performSegueWithIdentifier:#"mainSegue" sender:self];
}
else {
result.text = #"Wrong Password";
}
NSLog(#"%#", aString);
}
checkInput is getting called as soon as user enters the last digit. I am calling it on Editing did End of my fourth TextField.
Any help would be really appreciated.
Thanks
The trick is that these segues only work when your view controllers are "embedded" in a UINavigation controller. Select your first VC, then choose (from the top menu) Editor > Embed In > Navigation Controller.
It should automatically set itself as the initial VC, but double check that.
Note that in this picture I am showing how, but it is already done, so you can see that it automatically connected my main VC to the Nav Controller.
Another idea is to embed your main view in a navigation view controller and use it as initial vc. Then present the passcode vc modaly when its needed on top of the main vc. Another advantage of this way is that you now can use popToViewController:animated: which you can not use this way at the moment.
initial vc ----> main vc
|
|
modaly
|
|
pass code vc
i am developing an ios app and using storyboards generally. in my view controller named blackView (it has no xib file) there is a button to play video. that button goes to another view controller named videoViewController (videoViewController is not in storyboard it has xib file). after user clicks to done on the videoViewController user should return to the blackView and now all is work but after return to the blackView from videViewController the navigation bar and tab bar controllers of blackView not appear
how can i handle this? how can i get appear those navigation and tab bars on the blackView?
this is my code in my videoViewController.m file
- (void) moviePlayBackDidFinish:(NSNotification*)notification
{
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController * myview=[storyboard instantiateViewControllerWithIdentifier:#"blackView"];
[self presentModalViewController:myview animated:YES];
}
From your code i thing you are doing something wrong.
i need to ask one question to you that movieplayer in is presented od added as subview.
if you are presenting it then you need to do something like this in youe app.
- (void) moviePlayBackDidFinish:(NSNotification*)notification
{
[self dismissModalViewControllerAnimated:YES];
}
I have an application that pulls up a login page when it first starts. This login page goes over the application and does not let anyone through until they've logged in. I also have a settings tab on my main application that needs to lead back to this login screen. Right now it displays the login screen with the tab bar over it. Is there a way to get the login view over the tab bar?
I've done something similar by having views transitioning in over the top of my tab bar. I used yourView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal; with a 'close' button to let users return to the tab bar screen. I've not done this so it automatically comes in on app fire-up but instead call the view from a button on the screen separate to the tab bar controls. However, I'm sure you'll be able to utilise this somehow to do what you want.
In fact I've actually used this way of calling up views all over my app, each time it covers the tababr and you have to 'close' it to get back to main tabbed navigation you came from.
Try setting below in your viewDidLoad of login screen:
self.tabBarController.hidesBottomBarWhenPushed = YES;
You can do this by using a subclass of UITabBarController, which then performs various checks in viewDidAppear:. The login view is modally presented, as #Maxwell suggested.
// a subclass of UITabBarController
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self firstLoadChecks];
}
- (void) firstLoadChecks
{
if (!self.hasLogin) {
id login = [[[LoginViewController alloc] initWithDelegate:self autorelease];
id nav = [[[UINavigationController alloc] initWithRootViewController:login] autorelease];
nav.modalPresentationStyle = UIModalPresentationStyleFormSheet;
nav.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:nav animated:YES];
}
}
// from LoginViewControllerDelegate
- (void) didLogin
{
self.hasLogin = YES;
[self dismissModalViewControllerAnimated:YES];
}
// my LoginViewController can be closed without a login
- (void) dismissModalViewControllerAnimated:(BOOL) animated
{
[super dismissModalViewControllerAnimated:animated];
[self firstLoadChecks];
}