I've been fighting with this for hours. I've searched around everywhere and just can't seem to find the solution to my problem. I'm pretty sure I'm just lacking some key concepts here.
My AppDelegate (didFinishLaunching) basically sets up my window and invokes RootViewController:
// create our window
UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[window setBackgroundColor:[UIColor blackColor]];
// create our rootviewcontroller
RootViewController *controller = [[RootViewController alloc] init];
// add our rootviewcontroller's view to our window
[window addSubview:controller.view];
// controller is now owned by window's view
[controller release];
// show us to the world
[window makeKeyAndVisible];
When I add controller.view as window's subview, my understanding is that RootVC's loadView will automatically get called.
In RootVC loadView, I create a tabBarController, each tab having a navigationController and it's own viewController. All that is working fine.
In RootVC viewDidLoad, I'm checking to see if this is the first time a user is running this app, and if so, I want to throw up a modal welcome screen. This is the part I'm having trouble with.
I'd like to keep as much code out of the RootVC's viewDidLoad method, and ideally would be able to accomplish what I want with this:
WelcomeViewController *welcome = [[WelcomeViewController alloc] init];
[self presentModalViewController:welcome animated:true];
[welcome release];
Obviously this isn't working. WelcomeVC's loadView hasn't been run yet because I haven't explicitly set it's view property. I've played around with a bunch of different solutions (welcome.view - [[UIView....], using WelcomeVC's init method to set self.view) but I just can't seem to get that modal to pop up.
How should I accomplish what I'm looking for? What are the best practices, and what's the best solution to keep my code tight and tidy?
I'm stuck, so hopefully your solution will allow me to continue developing my app!
Although the problem is not so simple, the solution is. You have to wait until the main view appears. So check the condition and present your modal view in viewDidAppear method, not in viewDidLoad method.
Related
I'm building an app that does a ton of setup when the app loads. As such, I have a loading view that displays some information to the user while the setup takes place...
MyLoadingViewController *loadingViewController = [[MyLoadingViewController alloc] init];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.rootViewController = loadingViewController;
[self.window makeKeyAndVisible];
.
.
//Setup code happens here
.
.
MyHomeViewController *homeViewController = [[MyHomeViewController alloc] init];
self.window.rootViewController = homeViewController;
When the setup code completes, I want to transition into my home screen. Is setting the rootVewController to the new home view controller the proper way to do that kind of a transition?
Thanks so much for your wisdom!
Yes, that's an ok way to do it -- you won't have any animation, but it should work fine. loadingViewController will be deallocated after you switch the root view controllers -- if that's not what you want, then you should create a property to point to it.
I sometimes cheat a little by having the ViewController that does all the loading display an image that is the exact same as the default.png on screen while it loads, then animate it out once complete.
For example in my LoginViewController I firstly display the default image in viewDidLoad, contact the server for auto login and then fade out the image once the app logins. The user never notices as he just sees the default image fade out once the app is ready.
I'm in the process of making some adjustments to an app, including changing to a navigation-based interface. As part of that change I've written a view controller that contains a UINavigationController. The problem is, for some strange reason the UINavigationBar and UIToolbar managed by the UINavigationController are displaced 20px down from where they should be. I've managed to produce the following example that demonstrates the issue:
// MyAppDelegate.m
#implementation MyAppDelegate
#synthesize window = _window;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window.frame = [[UIScreen mainScreen] applicationFrame];
[self.window makeKeyAndVisible];
TestController* tc = [TestController new];
[self.window addSubview:tc.view];
return YES;
}
#end
// TestController.m
#implementation TestController
- (void)loadView
{
self.view = [[UIView alloc] initWithFrame:CGRectZero];
UINavigationController* navController = [UINavigationController new];
navController.view.backgroundColor = [UIColor blueColor];
[navController setToolbarHidden:NO animated:NO];
[self.view addSubview:navController.view];
}
#end
This produces the following result on my machine:
As you can see, the controls are 20px down from where I'd expect them to be. I've tried just about everything I can think of (various combinations of wantsFullScreenLayout, autoresizesSubviews, etc) with no positive effect. This also has nothing to do with programatically messing with the statusbar (as seems to be the case in most other examples of this I have come across), since I do not at any point mess with the statusbar. This occurs with or without a root view controller in the navigation controller - if there is one, it's contents are shifted 20px down too (so they actually are in the right place relative to the navigation bar and toolbar).
Any help much appreciated!
EDIT: After a bit of investigation, it seems that removing the line self.window.frame = [[UIScreen mainScreen] applicationFrame]; seems to correct the positioning of the navigation bar and toolbar and content. That said, now some other views in the application are in the wrong place (up underneath the statusbar). My understanding is that line is generally recommended to ensure that the window is the correct size?
As mentioned in my edit, removing the line self.window.frame = [[UIScreen mainScreen] applicationFrame]; seems to have corrected 95% of my problems. I've managed to fudge an approach to fix the other 5% by using the same background colour for my window and the remaining views having issues, but I can't say I'm thrilled with this solution - I shouldn't have to do that.
I'll keep experimenting, and if I find a better result will certainly post an edit here.
UINavigationController does not play nicely with being used as a subview; as you've noticed, it will often leave room for the status bar even when it is not actually under the status bar. If you're not trying to write your own container view controller, you should rework your code to not be adding a view controller's view as a subview at all.
That said, I've had luck fixing it by setting wantsFullScreenLayout to NO on the UINavigationController, which will make it not leave space for the status bar. You would, of course, want to do this just after allocating it, before loadView gets triggered.
I am using a tab based application that shows a presentModalViewController called "overview" that has 2 buttons on it .
In order to call it I am using the following code in app delegate:
Overview *overview = [[Overview alloc] initWithNibName:#"Overview" bundle:nil];
[self.tabBarController presentModalViewController:overview animated:YES];
When overview shows up, it has a button called that gets clicked and I am using the following code:
-(IBAction) btnLoginPressed{
[self dismissModalViewControllerAnimated:YES]; //get rid of view
Login *login = [[Login alloc] initWithNibName:#"Login" bundle:nil];
[self.tabBarController presentModalViewController:login animated:YES];
[login release];
}
However the login prsentModalViewController never shows up. Can someone explain why and what I can do to show it?
Thanks
When you present a modal view controller, you do it from the view controller currently in the view.
Assuming your second modal display of a view controller is happening in Overview.m change your code to the following:
-(IBAction) btnLoginPressed {
Login *login = [[Login alloc] initWithNibName:#"Login" bundle:nil];
[self presentModalViewController:login animated:YES];
[login release];
}
You don't need to dismiss Overview first, and in fact you shouldn't as it the animations won't work in conjunction with each other.
When you ultimately dismiss login (or however deep you want to go), you send dismissModalViewController:animated: as high up as you need to. To get back to the tab bar's controller use:
[self.tabBarController dismissModalViewController:animated]
It would be well beyond the scope of your question and the time I have to answer but you should take some time and really study the docs on implementing View Controllers. I definitely recommend following Apple's code style guidelines as one suggestion to make your code much more readable (e.g. overviewViewController vs overview). It's also clear you're just learning so keep at it.
I have a app which uses two UINavigationControllers - one for the menu system and one for the actual game. A common UINavigationController is declared in my appDelegate. When the app loads, it either loads the Menu or the Game's UINavigationController. And of course the player can then navigate between the two.
When going from the menu to the game, I create a new UINavigationController and present it as follows:
GameViewController *rootController = [[GameViewController alloc] init];
UINavigationController *newNavController = [[UINavigationController alloc] initWithRootViewController:rootController];
[rootController release];
newNavController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:newNavController animated:YES];
[newNavController release];
However, I've noticed that when I do this, the Menu's viewController never calls dealloc. Presumably because there's still a reference to something keeping it alive. I've found that when I explicitly set the App Delegate's UINavigationController to the new navigation controller, (before releasing the new navController) it releases the Menu. I do this as follows:
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.navController = newNavController;
[newNavController release];
Is this good practice? I've found that when navigating from the game back to the menu however, the same trick doesn't seem to work. I.e.
MainMenuViewController *menuViewController = [[MainMenuViewController alloc] init];
UINavigationController *newNavController = [[UINavigationController alloc] initWithRootViewController:menuViewController];
[menuViewController release];
newNavController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentModalViewController:newNavController animated:YES];
//Setting the appDelegate's navController to the new navController allows the menu to dealloc.
//This must happen AFTER the newNavController has been loaded.
MyAppDelegate *appDelegate = (MyAppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.navController = newNavController;
[newNavController release];
never call's dealloc on the game's main ViewController. And when I navigate back to the game again, the menu's main ViewController is no longer released either.
Am I missing something when juggling UINavigationControllers?
Thank you,
Michael
EDIT: I've since realised that the reason my game's main ViewController was not deallocing, was because I has some NSTimers that I hadn't invalidated! However, I'm still curious to know if my above approach is correct, and that explicitly redefining the navController in the App Delegate is the correct way to allow the different UINavigationControllers to dealloc :)
Make your life easier. Use a single UINavigationController, and maintain two separate View Controller stacks, which are just arrays of UIViewControllers. You can use [UINavigationController setViewControllers:animated:] to just swap the stacks out and leave the nav controller in place. Use UINavigationController.viewControllers to get the current stack to hold onto before replacing it. It's something like 14.3 billion times easier and cleaner than dealing with all the vagaries of multiple nav controllers.
I'm trying to open the email view controller (MFMailComposeViewController), and everything I read suggests using presentModalViewController:animated:, which seems to need to be sent to a UIViewController.
For example, in the documentation, it says:
Call the presentModalViewController:animated: method of the current view controller, passing in the view controller you want to present modally.
But I don't have a "current view controller"! My app is otherwise entirely OpenGL, and my setup code looks like:
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view = [[IPhoneView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[window addSubview: self.view];
[window makeKeyAndVisible];
and I set up the OpenGL context in the IPhoneView class.
I've found a number of questions asking how to get a UIViewController from a UIView, and the consensus seems to be: don't. So how can I open the email view controller with that nifty sliding animation?
You can always do your own sliding animation of a view and just pass MFMailComposeViewController to it: Hidden Drawer example
See a similar sliding code here as well: How can I present a UIView from the bottom of the screen like a UIActionSheet?