When my app first loads, I set the rootViewController property of my UIWindow to controllerA.
Sometime during my app, I choose to change the rootViewController to controllerB.
The issue is that sometimes when I do a flip transition in controllerB, I see controllerA's view behind it. For some reason that view isn't getting removed. Whats even more worrying is that after setting the rootViewController to controllerB, controllerA's dealloc method never gets fired.
I've tried removing the subviews of UIWindow manually before switching to controllerB, that solves the issue of seeing controllerA's views in the background but controllerA's dealloc still never gets called. Whats going on here????
Apples docs say:
The root view controller provides the content view of the window. Assigning a view controller to this property (either programmatically or using Interface Builder) installs the view controller’s view as the content view of the window. If the window has an existing view hierarchy, the old views are removed before the new ones are installed.
UPDATE
Here's the code of my AppDelegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self showControllerA];
[self.window makeKeyAndVisible];
return YES;
}
- (void)showControllerA
{
ControllerA* a = [ControllerA new];
self.window.rootViewController = a;
}
- (void) showControllerB {
ControllerB* b = [ControllerB new];
self.window.rootViewController = b;
}
It turns out there are two separate issues. 1) I had a retain cycle in Controller A so it was never getting dealloc'd. Secondly, in order to change the root view controller you must remove the windows subviews first (even though the docs suggest otherwise)
The problem could be in your implementation of ControllerA or ControllerB, they may retain 'self' in the code so ARC cant automatically dealloc you ViewController. Can you post you ControllerA and ControllerB implementation.
var loginNavigationController: OnBoardViewController?{
willSet{
if newValue == nil {
loginNavigationController?.view.removeFromSuperview()
}
}
}
loginNavigationController = nil
It's apple's bug, we assume ViewControllerA as the current rootViewController:
// ViewControllerA.m
- (void)buttonClick {
[self dismissViewControllerAnimated:YES completion:^{
// [((AppDelegate *)[[UIApplication sharedApplication] delegate]) resetRoot]; // OK
}];
[((AppDelegate *)[[UIApplication sharedApplication] delegate]) resetRoot]; // ViewControllerA's view will not dealloc
}
// AppDelegate.m
- (void)resetRoot {
ViewControllerB *controller = [[ViewControllerB alloc] init];
self.window.rootViewController = controller;
}
If reset window's rootViewController as this code, the ViewControllerA's view will never dealloc.
An even simpler solution is to set the backgroundColor of your new window to .white or any color. The default is nil, which results in a transparent background. That is why the older window (on top of which the new one is made visible) is being seen through.
Related
I currently have a launch screen storyboard with an image set up as my loading screen.
I'm used to doing everything programmatically, and storyboarding is throwing me for a ride. I have a boolean set up to determine if this is the first time the app is being launched. If so, I want to have one view controller that I set as the root of my navigation controller. If it isn't the first launch, I want to set a different root view controller to my nav controller.
I've clicked and dragged a navigation controller onto my storyboard. I just don't know what to do with it now. I have a viewController set up with an image that I'd like to be the first thing seen if it is the first time the app is being launched. However, I'm not sure what is automatically initialized using storyboard, so conceptually I'm not sure how to set up my app.
Since I have to handle this condition programmatically, do I even need to create a nav controller in storyboard, or would that be redundant?
My parent VC (viewController) is just an image with a label overlaid on top. Building with the below code in my appDelegate.h file correctly shows my launch screen storyboard, but then fades to a black screen.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
firstLaunch = YES;
if (firstLaunch == YES) {
ViewController *launchScreen = [ViewController new];
UINavigationController * navControl = [[UINavigationController alloc]initWithRootViewController:launchScreen];
self.window.rootViewController = navControl;
firstLaunch = NO;
}
else {
// create instance of other view controller and set as root of navigation controller
}
return YES;
}
Any ideas as to why my app is just navigating to a black screen?
Make sure to use
[window makeKeyAndVisible];
I usually do this in my application like so:
self.window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
self.navController = [[UINavigationController alloc]init];
self.window.rootViewController = self.mainViewController;
[self.window makeKeyAndVisible];
In addition, your code will always say it's the user's first time. I would recommend using NSUserDefaults as such:
BOOL hasLaunchedBefore = [[NSUserDefaults standardUserDefaults] boolForKey:#"HasLaunchedBefore"];
if (!hasLaunchedBefore) {
//All your first launch UI stuff
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"HasLaunchedBefore"];
[[NSUserDefaults standardUserDefaults] synchronize];
} else {
//Not first launch
}
I am using a storyboard in Xcode 5, which appears as so:
My requirement is to push a ViewController (VIEW1 or VIEW2) into view from the app delegate. Essentially it should not matter what view is presently on the screen -- I would just like to make a ViewController appear when the app delegate picks up an external event.
In order to try and achieve this, I have property references to both the TabBarCtrl-Products and NavCtrl-ProductA in my app delegate.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
_tabBarProducts = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"sidTabBarProducts"];
NSArray *tabvcs = _tabBarProducts.viewControllers;
for (id controller in tabvcs){
if ([controller isKindOfClass:[VCNavControl_ProductA class]]) {
_navControllerProductA = controller;
break;
}
}
return YES;
}
The app delegate method to push VIEW2 is:
-(void)showVCVIEW2
{
VC_V2 *targetvc = nil;
targetvc = [[UIStoryboard storyboardWithName:#"Main" bundle:nil] instantiateViewControllerWithIdentifier:#"sidView2"];
[[AppDelegate sharedInstance].navControllerProductA pushViewController:targetvc animated:NO];
}
This works OK when VIEW1 is showing at the time showVCVIEW2 is called, however it does not work when ViewCtrl-ProductB is showing. I can see that the new instance of targetvc has been added to the AppDelegate _navControllerProductA's stack, however it does not display.
(Regarding the setting of the the app delegate's rootViewController, I set this to _tabBarProducts after the VC-Splash and VC-Setup ViewCtrls have finished).
I would appreciate very much if anyone can give me an idea on how to achieve this. I suspect my problems stem from having a NavCtrl in a TabBarCtrl, but I do not know a way around this.
Your problem is that the navigation controller you are pushing on is not in the view hierarchy.
You instead could try setting the tabBarController's selected index like this:
[self.tabBarController setSelectedIndex:1];
I've been receiving a warning that is related to my use of the GKTurnBasedMatchmakerViewController and the BannerViewController for iAd. That warning is:
Presenting view controllers on detached view controllers is discouraged <RootViewController: 0x14cd143c0>
What is wrong with the sequence of code shown below that causes this warning?
In AppDelegate.h
#interface AppDelegate : NSObject <UIApplicationDelegate> {
UIWindow *window;
RootViewController *viewController;
}
In AppDelegate.m
- (void) applicationDidFinishLaunching:(UIApplication*)application {
viewController = [[RootViewController alloc] initWithNibName:nil bundle:nil];
[window setRootViewController:viewController];
[window addSubview: viewController.view];
}
Then when the user presses a Play button on my home screen, I first open the game center view controller as follows (notice the 2nd line is setting presentingViewController equal to the rootViewController that was set up in the appDelegate):
AppDelegate * theAppDelegate = (AppDelegate *) [UIApplication sharedApplication].delegate;
self.presentingViewController = theAppDelegate.viewController;
GKTurnBasedMatchmakerViewController *mmvc = [[GKTurnBasedMatchmakerViewController alloc] initWithMatchRequest:request];
[presentingViewController presentViewController: mmvc animated: YES completion:nil];
After the above code runs, a new scene is loaded. This scene is described in my GameSelectionLayer.h as follows:
#interface GameSelectionLayer : CCLayer <InAppStoreControlLayerDelegate> {
...
RootViewController *viewController;
AppDelegate *app;
BannerViewController *bannerViewController;
}
Then in my GameSelectionLayer.mm, I load the bannerViewController onEnter as follows:
-(void)onEnter {
[super onEnter];
app = (AppDelegate *)[[UIApplication sharedApplication] delegate];
viewController = [(AppDelegate *)[[UIApplication sharedApplication] delegate] viewController];
bannerViewController = [[BannerViewController alloc] initWithContentViewController:viewController];
app.window.rootViewController = bannerViewController;
}
I receive the above mentioned warning when the above code runs. Please let me know what you think I might be doing wrong that is causing this warning.
The problem is you are replacing the view hierarchy of a view controller which has another view controller presented. The "detached" warning comes when you try to present a view controller from a view controller which is not attached to a window, or does not have a descendent view controller attached to a window.
Here I recommend two approaches. Either dismiss all view controllers before replacing the root view controller, or, the better of the two options, use another window with its own root view controller hierarchy.
Also, note that you should not add the view controller's view as a subview of the window. The system does it for you when you set the view controller as the root view controller.
I have a problem with my iOS app. It is based on storyboards. So to set the rootViewController property it should be enough to set the "Initial View Controller" property in Interface Builder and the MainInterface-Property in the project settings to the name of my storyboard. Still I always get the message "Application windows are expected to have a root view controller at the end of application launch".
I do several things in the applicationDidFinishLaunching section but even if everything except return YES; is commented out, I get the message.
How can I fix this warning? Or can I ignore it as everything works?
Thanks a lot.
Are you using an activity indicator in your app delegate or root view controller by chance? If so, it might be setting itself as the root. Move the display of the indicator to somewhere after your main views are set up.
Try this code :
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions{
UIWindow *window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
InitialViewController initial = [self.storyboard instantiateViewControllerWithIdentifier:#"STORYBOARDID"];
window.rootViewController = initial;
[window makeKeyAndVisible];
return YES;}
I have been having some memory management issues and random crashes with my app. I have done a lot of work on it to try to clean up the code generally and have converted the project to ARC.
I now have a clear view on the problem - essentially the app does not release views so as a user moves through the app each view is reloaded and retained until finally the app crashes due to memory issues.
I have a UINavigationController. My app runs only in landscape left orientation. When i use
[window setRootViewController:viewController];
on load and then
[self.window addSubview:[finalViewController view]];
the new view is displayed in portrait - if i rotate it to landscape left with code when i load it in, then all kinds of other random issues come up.
If instead of addSubview i use
[self.viewController.view removeFromSuperview];
[self.window setRootViewController:finalViewController];
viewController = nil;
self.viewController = nil;
window.viewController = nil;
rotation is ok but views are not released and i have a memory issue with the app and it crashes eventually. Any thoughts would be awesome - appreciate i'm probably missing something fairly basic here. Thanks & happy holidays!
How are you loading new views in your app? If you are using a UINavigationController, your AppDelegate should start something like this:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
RootViewController* rootController = [[RootViewController alloc] init];
UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:rootController];
[self.window setRootViewController:navController];
[self.window makeKeyAndVisible];
return YES;
}
To load another view(say from a button press) you will do something like this from within the root view:
SecondViewController *secondView = [SecondViewController alloc] init];
[self.navigationController pushViewController:secondView animated:YES];
This will make the UINavigationController responsible for memory management of your views.
As for rotation, that is handled by giving each of your ViewControllers this method:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
if(interfaceOrientation == UIInterfaceOrientationLandscapeLeft) return YES;
return NO;
}
Aslong as you are using the UINavigationController the way it is meant to be used, you should not have any non-releasing views. You should read into the UINavigationController: http://developer.apple.com/library/ios/#documentation/uikit/reference/UINavigationController_Class/Reference/Reference.html
Another possibility is that the childviews of your view controllers have strong references to their parent view/controller. This will stop a parent viewcontroller from deallocating due to it giving its child a retain count of 1 and the child giving the parent a retain count of 1 as well. Here is a SO post with information on strong & weak references: Objective-C declared #property attributes (nonatomic, copy, strong, weak)