I just inherited a project that uses a Storyboard + nibs.
The Storyboard had a navigation controller set as the initial view controller, and then had the first view controller set as its root. Very simple.
I decided to create a new view controller and make that the new root controller. I still have the old root view controller, its just not the root anymore. So for speed, I deleted the navigation controller, selected my new root view controller, and embedded it inside of a navigation controller.
Worked perfectly. Here's what I don't understand...
My setup looks identical to what was there before, but I had to add the following to the app delegate:
LaunchController *launch = [[LaunchController alloc] initWithNibName:#"LaunchController" bundle:nil];
UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:launch];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self.window setRootViewController:navigation];
self.window.backgroundColor = [UIColor whiteColor];
[self.window makeKeyAndVisible];
Why did I have to add this, but the previous version didn't need it, even though I feel like the changes I made still kept the setup identical to what I had before?
Throughout the app, performSegueWithIdentifier is being called and now those always fail and say "Receiver has no segue with identifier."
If I remove the above code from the app delegate, then my root controller that's both a nib and on the Storyboard doesn't show on screen. It loads, but you can't see anything. However, now all of my segues work.
So what's going on here? I realize I probably screwed something up when creating a new navigation controller and root view controller, but I can't figure this out.
I have been spending some more time working with nibs, and I've been adding the above app delegate code, so I really don't even understand how the previous developer was making this work in the first place without that code in the app delegate.
Any help is greatly appreciated.
If you're using storyboard then you don't have to write any code in App Delegate class. Instead you mention your storyboard like Project file -> Targets -> General -> Deployment Info -> Main Interface.
Here you see Main_iPhone is my storyboard for the project. Now any View Controller inside that storyboard which is set as "Is Initial View controller" will be loaded by the app automatically for you.
I have added a Storyboard file to an app that initially had none. For some reason, I could not get my custom UIViewController to display correctly until I added this into didFinishLaunchingWithOptions:
ActivityViewController *viewController = [[UIStoryboard storyboardWithName:#"MainStoryboard" bundle:NULL] instantiateViewControllerWithIdentifier:#"ActivityViewController"];
Why do I need to force the use of my storyboard like this? The iOS template projects (Single View, Master-Detail etc) doesn't need this.
Checklist:
Xcode Project Summary→Main Storyboard is set correctly to "MainStoryboard".
Interface Builder→Identity Inspector→Class is correctly set to "ActivityViewController".
Interface Builder→Identity Inspector→Storyboard ID is also set to "ActivityViewController", but this is only because it's needed by instantiateViewControllerWithIdentifier.
You do not need to call instantiateViewControllerWithIdentifier if you set Is Initial View Controller on your "ActivetyViewController" in the storyboard. The initial view controller will have an arrow pointing at it.
I have an application created from the tabbed application template. (ARC, iOS 4)
There are several tabs and there is a button on the 2. tabs viewcontroller.view(ViewCont2).
This button loads another viewcontroller's(ModalViewCont) view by presentModalViewController method.
There is a close button on ModalViewCont which calls dismissModalViewControllerAnimated.
In viewDidDisappear of ViewCont2, i am setting self.view = nil and other outlets to nil to unload the view so it will be fresh loaded next time it appears on screen. I am doing this because it inherits from a base class(BaseViewCont) which initializes some general properties of the view controller and adds some buttons, labels etc. in viewDidLoad method. So, ViewControllers that inherit from this base class may configure those properties differently as they wish in their viewDidLoad method.
Problem
Now, when ModalViewCont on screen, pressing the Home button to put application in background and after getting the application back, closing the ModalViewCont does not bring back the ViewCont2's view but a black screen with the tabbar at the bottom. The same thing happens without putting the application background/foreground; if other tabs tapped before tapping the 2. tab.(EDIT : This happens only if self.view set to nil in viewWillDisappear instead of viewDidDisappear.)
I determined that ViewCont2 loads a new view (checked it's reference) but view's superview is nil so the new view is not displayed but a black screen.
Things that did not work
Using [self.view removeFromSuperview]; before setting self.view=nil,
In viewWillAppear adding view to the parent; [self.parentViewController.view addSubview:self.view]; This one did not work smoothly, view placed slightly up of the screen. This is because there are several other superviews in the hierarchy.
Solutions i considered;
1- If superview is nil in viewDidLoad, it becomes available in viewWillAppear (assumption). So, viewWillAppear method of ViewCont2 could be used to get the superview loaded correctly by the following;
_
if (self.view.superview == nil)
{
self.tabBarController.selectedViewController = nil;
self.tabBarController.selectedViewController = self;
}
2- viewWillAppear method of base class could be used instead for initialization so there is no need to unload the view. So, performance could be optimized, it will not be unloaded each time view disappears. Also, it would be better to perform initialization only once by checking a flag, instead of performing it every time it appears.
Questions
1- Why does not the superview restored? What should i do for it? (This is the main problem i want to understand and solve instead of trying alternatives...)
2- Am i doing something wrong by assigning nil to view for unloading it? If so, how should i unload the view properly in such case like this(tabbed application)?
3- Is anything wrong with the 1. solution? Does it seem like a kludge? Is that assumption about superview and viewWillAppear correct?
EDIT : It seems that when viewDidLoad is called earlier than it should(i.e when view nilled in viewWillDisappear instead of viewDidDisappear), superview is not set.
It seems weird, but your suggestion (1) is indeed a correct workaround for this problem:
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
if (!self.view.superview) { // check if view has been added to view hierarchy
self.tabBarController.selectedViewController = nil;
self.tabBarController.selectedViewController = self;
}
}
Your second suggestion is good for performance (because view loading is an expensive operation) - but it will not solve the problem. You can also end up with a black screen without setting the view to nil in the following situation (test this in the iOS simulator):
open the modal view
simulate a memory warning -> this will unload the views in the tabbarcontroller
press home button and open the app again
close modal view -> black screen
Generally you can assume that in viewDidLoad the view property is set and in viewWillAppear + viewDidAppear the view has been added to the view hierarchy; so the superview should be there at that time (Here the superview is a private view of the tabbarcontroller of class UIViewControllerWrapperView). However in our case, although the view is reloaded (at the time of app resume), it is not added to the view hierarchy resulting in a black screen. This seems to be a bug in UITabBarController.
The workaround forces the appearance selectors to be performed again. So viewWillAppear will be called again, this time with a superview in place. Also viewDidAppear will be called twice!
Setting self.view to nil is okay, but should not be necessary in most cases. Let the system decide when to unload the view (iOS can unload views when memory gets low). The view controller code should be designed in a way so that the UI can be reconfigured at any time without reloading the view.
You do not have full control over when views are loaded and unloaded, and you are not supposed to load/unload views manually yourself.
Instead, you should think of view loading/unloading as something that's entirely up to your UIViewControllers, with you being responsible only for:
Implementing the actual loading, by associating your UIViewController subclass with a nib file or by implementing loadView manually.
Optionally implementing the viewDidLoad, viewWillUnload and viewDidUnload callbacks, which are called by the view controller when it decides to load/unload its view.
The fact that you have no full control of when the above callbacks will be called, has implications about what should go into them.
In your case, if I understand correctly, whenever your ViewCont2's view disappears, you want to reset it so that when it reappears it will be in some "clean" state. I would implement this state reset in some method, and call it both from viewDidLoad and from viewDidDisappear. Alternatively, you can have the "clean" logic in viewWillAppear.
Or maybe you want to clean ViewCont2's view only when the present button is tapped? In that case, clean the view both in viewDidLoad, and when the button is tapped.
What I offer is that when the modal view controller is active, and you dismiss the view, that you add a new view to the navigation view controllers viewControllers, then that view is told to remove its predecessor.
You can play with my project to see if you think it works for you.
EDIT: my comment on the selected answer is that this technique obviously works now, but I myself am having a hard time followiing it. The code in my project uses the system in a simple and direct fashion - when the modal view is told to dismiss itself, it calls a method (could be in any class) that adds a new view to the navigation controller's array, then dismisses itself. For a bit of time there are two view controllers of the same time, the new one stacked over the old one. When the new view controller appears, based on seeing a flag it silently and behind the scenes removes the undesired viewController from the nab bar's stack, and poof, it goes away.
I have found the actual solution to the UITabBarController bug(memory warning,app enter back/foreground,dismiss modal). Using UITabBarController as the root view controller is the cause of the bug. So, we could use another view controller as the root view controller and present the tab bar from it. I have tested it on iOS 5.1 simulator.
Of course, the overhead of extra UIViewController is subject to debate. Also, it's against the Apple documentation;
Unlike other view controllers, a tab bar interface should never be installed as a child of another view controller.UITabBarController Class Reference
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// A root view controller other than the actual UITabBarController is required.
self.window.rootViewController = [[UIViewController alloc] init];
[self.window makeKeyAndVisible];
self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers = [NSArray arrayWithObjects:viewController1, ..., nil];
[self.window.rootViewController
presentModalViewController:self.tabBarController animated:NO];
}
I have found other solutions;
First one causes the warning: "Application windows are expected to have a root view controller at the end of application launch" although there is root view controller.
Although it seems kludgy, the temporary view controller will be released with the first one.
Second one seems more reasonable.
.
- (void) tabBarBlankScreenFix1
{
self.window.rootViewController = [[UIViewController alloc] init];
[self.window makeKeyAndVisible];
[self.window addSubview:self.tabBarController.view];
self.window.rootViewController = self.tabBarController;
}
- (void) tabBarBlankScreenFix2
{
self.window.rootViewController = [[UIViewController alloc] init];
[self.window makeKeyAndVisible];
[self.window addSubview:self.tabBarController.view];
}
I think you shouldn't assign the view to nil.
If I understand you right you want to refresh/reload content every time the view appears.
So instead of setting the view to nil, you should try to refresh it. You can do it by adding:
- (void)viewWillAppear{
[self.view setNeedsDisplay];}
Please tell me if I understand your issue right
I am learning iPhone development and in my application I have some view but I should use a window, so I want to call a window in an IBaction how can I call a window? I try to use the example with AppDelegate you can see my code :
- (IBAction)start:(id)sender {
self.window = [[[Game1ViewController alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
[self.window makeKeyAndVisible];
}
Game1ViewController is type UIWindow.
Best Regards
I believe your app only gets one window. On top of this you place different views. These can be anything that subclasses UIView. These can be controlled by a UIViewController. Usually there is some sort of design style that dictates how the app is structured, between UINavigationController, UITabController, Master/detail.
If you open Xcode and start with one of their templates such as master/detail you can see the transitions between views and how to make one appear/disappear and the interaction between view and view controller .
I had the entire app (it consists of one view) set up and ran from a View in the ViewController object of the MainWindow nib, INSTEAD of actually having the view ran from the actual ViewController nib.
The app runs flawlessly, however I have two warnings. I learned that these warnings are caused by me running everything from the MainWindow nib rather than a ViewController.
Are there any issues with running an app from the MainWindow rather than an actual ViewController? The app is text based and involves constantly updating UILabels. You can find it here if you'd like to take a look at what I'm talking about.
Right now having it set up this way causes no issues... however I am planning on expanding the app. I tried copy and pasting my Scroll View to the actual view controller nib, but when I did I just had a grey screen. Can I leave the app as it is?
But what is there in your MainWindow.xib file? If you just want to have window object, go to your applicationDidFinishLaunching method and create a window object.
- (void)applicationDidFinishLaunching:(UIApplication *)application{
UIWindow *appWindow = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window = appWindow;
[appWindow release];
}
Or you could have select the option Window-Based application while creating a new project.