I am developing an iPad application where the user just can (in some cases) open the app, if he types in a password.
Therefore I need something like a LoginViewController. But first, let us handle the common case, where the app just needs to display the HomeViewController:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
//...
self.controllerHomeView = [[HomeViewController alloc] initWithNibName:#"HomeView" bundle:nil];
self.controllerHomeView.showInitialGuide = isFirstLaunch;
self.window.rootViewController = controllerHomeView;
[self.window makeKeyAndVisible];
//..
}
But, like I said before, some users may have defined a password and if so, we need to display a login screen. That's what I did to enable such a feature:
- (void)applicationDidBecomeActive:(UIApplication *)application
{
//---
if(isAppPinSecured && !loginIsAlreadyDisplaying) {
LoginBackgroundViewController *controllerLoginBG = [[LoginBackgroundViewController alloc] initWithNibName:#"LoginBackgroundView" bundle:nil];
self.appPinInputBG = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Default-Landscape#2x~ipad.png"]];
self.appPinInputBG.frame = CGRectMake(0, 0, 1024, 748);
self.appPinInputBG.userInteractionEnabled = YES;
[controllerLoginBG.view addSubview:self.appPinInputBG];
//present new root = background
self.window.rootViewController = controllerLoginBG;
//...
}
}
What I am doing is, changing the root view controller to LoginViewController and if the user puts in the correct password, changing back from the LoginViewController to the HomeViewController.
Everything works fine so far, except the orientation. If the current interface orientation is unknown, because the iPad e.g. is laying on a table, the LoginViewController orientation is LandscapeRight instead of the one in the HomeViewController (LandscapeLeft).
It works properly if holding the iPad in your hands, but otherwise precisely not.
Any suggestions on how to fix that issue? I did set my app orientation in the plist file (Landscape Left). I do implement shouldAutorotate with UIInterfaceOrientationIsLandscape(...) in both - Home- and LoginViewController.
Thanks in advance!
Argh, the problem was, that I tried to push a view controller modally before viewDidAppear was called -> you should never do this...
After changing my code due to this error it worked like a charm
I think what is happening is that the preferred orientation is set on the initial root controller - when you switch it out, it doesn't get set.
I would suggest you consider always making HomeViewController the root controller. And instead of switching the root controller, push the LoginViewController (with no animation if you want it to show up instantly):
[self.navigationController pushViewController:controllerLoginBG animated:NO]
That way you'll be able to do things like logout and pop to the HomeViewController. It will also keep your view controller navigation consistent.
Related
I am presenting a tutorial view, from the AppDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
....
EERootVC *rootVC = (EERootVC *)self.window.rootViewController;
UINavigationController *navcon = (UINavigationController*)rootVC.contentViewController;
EETutorialRootVC *rootTutorialViewController = [rootVC.storyboard instantiateViewControllerWithIdentifier:#"EETutorialRoot"];
[navcon pushViewController:rootTutorialViewController animated:NO];
return YES
}
How can I dismiss this new view once completed?
I've tried this:
[self.navigationController popViewControllerAnimated:YES];
Which works, but the view it returns to seems to be cut off (shifted up?)
Its not going to be self because calling self in AppDelegate is referencing itself (which will be AppDelegate)
If you want to dismiss it from the AppDelegate you simply do the same thing you did to present it:
[rootTutorialViewController popViewControllerAnimated:YES];
But your more than likely not going to dismiss it in the AppDelegate, your more then likely going to dismiss it in the view controllers main class, which is where you would call self
Mmm, it seems you work with some custom setup that you have to provide insight in, so we can give you a better answer.
If you window.rootViewController would be an instance of UINavigationController you'd have pretty default behaviour and if you'd push and pop ViewControllers, everything would look good.
But you are getting the UINavigationController from a custom property of your window.rootViewController. That suggests that EERootVC is some custom ContainerViewController and my bet is something goes wrong in there where you add the EERootVC.contentViewController to it's view and set it's frame.
For example, set the UINavigationController as the Window's rootViewController and I bet it works as supposed too.
Other then that, you push it without animation, and dismiss it with animation. For a good user experience you probably want to think about that approach again.
I have a one time agreement view that will only show up when user launches app first time.
I use storyboard IOS 5.1 and My rootview is a navigationviewcontroller. My agreement view has no connection with navigation controoler I just want to present a modalview pop up then dismiss the view:
I use following code but it doesn't do anything app just continues and launches navigationviewcontroller I put flags, yes app enters the if statement. :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if(login==ok){
UIStoryboard *storyboard = [UIApplication sharedApplication].delegate.window.rootViewController.storyboard;
UIViewController *loginController = [storyboard instantiateViewControllerWithIdentifier:#"AgreementViewController"];
[self.window.rootViewController presentModalViewController:loginController animated:YES];
}
return YES;
}
How can I switch a viewcontroller that has no connection to storyboard and dismiss it?
In the app I've been working on last weekend I had a similar situation. I wanted to show a seperate storyboard the first time for a guided setup.
My code looked pretty much the same as yours and it gave me a warning when I was debugging it.
This was because of the fact that the rootViewController I was trying to present my one-time ViewController on, isn't visible at this point in the app.
So, instead of presenting my one-time view controller "on" the rootViewController, I decided to set my one-time view controller as the rootViewController.
In your case it would be something like:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if(login == ok) {
UIStoryboard *storyboard = [UIApplication sharedApplication].delegate.window.rootViewController.storyboard;
UIViewController *loginController = [storyboard instantiateViewControllerWithIdentifier:#"AgreementViewController"];
[self.window setRootViewController:loginController];
}
return YES;
}
To dismiss it, you could probably add a segue from your one-time view controller to your first view controller or you could use code similar to the one in your question.
Another approach would be to do the check and view switching in your first "normal" view's viewDidAppear, instead of in the AppDelegate.
Hopefully this will point you in the right direction.
I'm trying to create an iOS (iPhone specific) app where users have to authenticate before accessing the rest of the app. I'm using a Storyboard for the main application which looks like this (broken up into separate lines):
(1) -> NavigationController
(2) -(o-o)-> OfficesViewController
(3) -[<-]-> OfficeViewController
I also have a separate XIB file called ScanOverlayViewController which gets programmatically pushed by the OfficeViewController when a user presses on a button. Not sure if it's proper to have Storyboards and XIBs mixed in like this, but it's been working so far.
Anyway, back on topic, I want to put an authentication screen infront of the NavigaitonController, but I'm not sure how.
I've tried placing an unlinked view in the Storyboard and setting it as the initial view. I then placed a hidden button and made a segue to the NavigationController. In code I tried having the controller perform the segue but the seque wasn't actually being performed even though the view had appeared properly.
I then tried to make the authentication view it's own separate XIB and initializing the controller in the app delegate and setting its view as a sub view of the app delegate. That just resulted in the app going directly to the OfficesViewController view as if I hadn't done anything.
Is there a specific way of doing this properly so it works with Storyboards?
Thanks in advance!
UPDATE
Here's my current app delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
AuthenticationViewController *authenticationViewController = [[AuthenticationViewController alloc] initWithNibName:#"AuthenticationViewController" bundle:nil];
if (authenticationViewController) {
NSLog(#"Should have generated %#", authenticationViewController);
};
[[[application keyWindow] rootViewController] presentModalViewController:authenticationViewController animated:YES];
return YES;
}
The controller does get allocated and initialized, but it's just not showing up.
Have the NavigaitonController as your initial view controller, then present the authentication screen modally if required. You can do this in -application:didFinishLaunchingWithOptions:.
Mixing XIB files and storyboards is fine, storyboards get turned into XIB (or nib) files anyway, they just have some extra meta data. Mix and match as makes sense.
E.g.:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
if ([self needsAuthentication])
{
dispatch_async(dispatch_get_main_queue(), ^{
AuthVC *authVC = [[AuthVC alloc] init];
[[UIWindow keyWindow].rootViewController presentModalViewController:authVC animated:YES];
});
}
// anything else
return YES;
}
I have a split-view app that allows a user to select and display a thumbnail of a chosen image. I have placed a UIButton in the detailViewController using Interface Builder. When this button is pressed, I would like to have it change to a full screen view of the image. I have set up a new View Controller, called FullViewController and thought I had everything connected. The problem is that the navigation controller is null. I adjusted the AppDelegate.m to the following:
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after app launch.
// Set the split view controller as the window's root view controller and display.
self.window.rootViewController = self.splitViewController;
UINavigationController *nvcontrol =[[UINavigationController alloc] initWithRootViewController:fullViewController];
[window addSubview:nvcontrol.view];
[self.window makeKeyAndVisible];
return YES;
}
This is the function in the DetailViewController.m which is called when the button is pressed. The navigation controller comes up null in here.
//Function called when button is pressed - should bring up full screen view
- (IBAction) pressFullViewButtonFunction: (id) sender{
//viewLabel.text = #"Full View";
if (fullViewController == nil){
FullViewController *fullViewController = [[FullViewController alloc] initWithNibName:#"FullViewController" bundle:[NSBundle mainBundle]];
NSLog(#"fullViewController is %#", fullViewController);
self.fullViewController = fullViewController;
}
NSLog(#"self.navigationController is %#",self.navigationController);//this is null
[self.navigationController pushViewController:self.fullViewController animated:YES];
}
I'm not sure how to fix this. I've tried adding in the couple lines in the AppDelegate, but when it runs, the table in the root view doesn't show up and it no longer properly switches between portrait and landscape views.
I have the rest of the code readily available if that would help clarify. Just let me know!
Thanks.
From the code you post it is not possible to identify the problem, but two common reasons for self.navigationController to be nil are:
you did not push the object behind self on to the navigation controller in the first place; indeed it seems so, since the navigation controller is added as a subview of the split view controller; possibly you mean the opposite... not sure...
(sub-case of 1) you showed the object behind self using presentViewControllerModally.
When I say "the object behind self" I mean the instance of the class where pressFullViewButtonFunction is defined.
If you need more help, post the code where you push your controllers on to the navigation controller...
On a side note, if you do:
UINavigationController *nvcontrol =[[UINavigationController alloc] initWithRootViewController:fullViewController];
and nvcontrol is not an ivar, then you have a leak.
Hope this helps...
I am trying to display a UISplitViewController presenting it as a Modal View Controller in my iPad app. I manage to have it display, but for some reason there is a gap to the left of the modal view the size of a Status Bar which is also preserved when the orientation is changed.
Does anybody know why this is happening? Or if this is even possible? Maybe I'm just digging myself a huge hole.
Like for many of you, I needed a 'modal way' to use the UISplitViewController. This seems to be an old issue, but all I found in StackOverflow was at best an explanation why the problem happens when you attempt to do so (like the accepted answer above), or 'hack-arounds'.
However, sometimes it is also not very convenient to change much of your code-base and make a UISplitViewController the initial object just to get it's functionality up and running.
In turns out, there's a way to make everybody happy (including Apple guidelines). The solution that I found best, was to use the UISplitViewController normally, but when needed to be shown/dismissed, use the following approach:
-(void)presentWithMasterViewController: (UIViewController *) thisMasterViewController
andDetailViewController: (UIViewController *) thisDetailViewController
completion:(void(^)(void))completion
{
masterViewController = thisMasterViewController;
detailViewController = thisDetailViewController;
[self setViewControllers:[NSArray arrayWithObjects:masterViewController, detailViewController, nil]];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.autoresizingMask = UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight;
self.window.rootViewController = self;
[self.window makeKeyAndVisible];
if(completion)
completion();
}
-(void)dismissViewControllerWithCompletion:(void (^)(void))completion
{
self.window = nil;
masterViewController = nil;
detailViewController = nil;
if(completion)
completion();
}
Where "window", is a property of your UISplitViewController subclass. And the system will take care of the rest!
For convenience/reference, I uploaded this as a UISplitViewController subclass to gitHub:
ModalSplitViewController
--EXAMPLE ON HOW TO USE --
mySplitViewController = [[ModalSplitViewController alloc] init];
mySplitViewController.delegate = self;
[mySplitViewController presentWithMasterViewController:masterViewController andDetailViewController:detailViewController completion:nil];
// when done:
[mySplitViewController dismissViewControllerWithCompletion:nil];
mySplitViewController = nil;
Side-note: I guess most of the confusion originates from the fact that
the UISplitView usage example from Apple documentation uses the window
created in the appDelegate, and for the fact that most people are not
so familiar with the window concept - because we normally don't need
to (they are created once in StoryBoards or boilerplate code).
Additionally, if you are doing state restoration, one should not
forget that programmatically-created UIViewControllers won't
automatically be restored by the system.
The stock UISplitViewController was designed for use as the root view controller only. Presenting one modally goes against the Apple Human Interface Guidelines and has a high probability of getting rejected by the App Review Team. In addition, you may receive the error:
Application tried to present Split View Controllers modally
Technically, this is what I did:
1/ Subclass a UIViewController ie. #interface aVC: UIViewController
2/ In the viewDidLoad, set up a splitViewController, ie. aSplitVC
3/ Then self.view = aSplitVC.view
After all, present aVC as modalViewController
I agree with Evan that this is slightly off-color for Apple, but I was able to complete a working version of this with the following solution:
UISplitViewController *splitVC = [[UISplitViewController alloc] init];
splitVC.delegate = VC2;
splitVC.viewControllers = [NSArray arrayWithObjects:navcon1, navcon2, nil];
UINavigationController *splitNavCon = [[UINavigationController alloc] init];
splitNavCon.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[splitNavCon.view addSubview:splitVC.view];
VC2.splitParentViewController = splitNavCon;
[self presentViewController:splitNavCon animated:YES completion:nil];
This allowed me to have a working back button in the new UISplitViewController that was presented modally on the screen.
You'll notice that I actually pass the VC2 (the delegate of the UISplitViewController) its parent UINavigationController. This was the best way that I found I could dismiss the UISplitViewController from within the VC2:
[splitParentViewController dismissViewControllerAnimated:YES completion:nil];
I believe one can do the other way around: instead of custom controller presenting split controller, one can set up the split controller as the root window controller in storyboard, and on top of its view you can add your custom controller (ie, login screen) and remove it from the screen (removeFromSuperview for example) when it is needed.
That answer is not actually correct, because it not valid any longer since iOS8 and if you need to support even iOS7 you can do that like you put actually modally UIViewController which has a container as SplitView.
let mdSplitView = self.storyboard?.instantiateViewControllerWithIdentifier("myDataSplitView") as! MyData_SplitVC
self.addChildViewController(mdSplitView)
mdSplitView.view.bounds = self.view.bounds
self.view.addSubview(mdSplitView.view)
mdSplitView.didMoveToParentViewController(self)