Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

black screen - superview is nil in viewWillAppear

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.

like image 385
lockedscope Avatar asked Aug 16 '12 13:08

lockedscope


3 Answers

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):

  1. open the modal view
  2. simulate a memory warning -> this will unload the views in the tabbarcontroller
  3. press home button and open the app again
  4. 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.

like image 82
Felix Avatar answered Nov 13 '22 13:11

Felix


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.

like image 26
Danra Avatar answered Nov 13 '22 14:11

Danra


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.

like image 25
David H Avatar answered Nov 13 '22 13:11

David H