12

I have a navigation controller. One of the views adds custom subviews in its viewDidAppear:. I notice that the first time I navigate to an instance of this view controller after launching the app, viewDidAppear: invokes twice. If I pop this view off the stack and navigate to it again, viewDidAppear: invokes only once per appearance. All subsequent appearances invoke viewDidAppear: once.

The problem for me is that the first time I get to this view I end up with twice the number of subviews. I work around this problem by introducing a flag variable or some such, but I'd like to understand what is happening and how come I get two invocations in these circumstances.

iter
  • 4,171
  • 8
  • 35
  • 59

8 Answers8

20

You should never rely on -viewWillAppear:/-viewDidAppear: being called appropriately balanced with the disappear variants. While the system view controllers will do the best they can to always bracket the calls properly, I don't know if they ever guarantee it, and certainly when using custom view controllers you can find situations where these can be called multiple times.

In short, your -viewWillAppear:/-viewDidAppear: methods should be idempotent, meaning if -viewDidAppear: is called twice in a row on your controller, it should behave properly. If you want to load custom views, you may want to do that in -viewDidLoad instead and then simply put the on-screen (if they aren't already) in -viewDidAppear:.

You could also put a breakpoint in your -viewDidAppear: method to see why it's being called twice the first time it shows up.

Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
  • I have a breakpoint in `viewDidAppear:`. How can it answer the why question? – iter Jul 07 '10 at 22:04
  • Look at the stack trace to find out why the system is calling this method. If the backtrace is different for both of the calls, you can use this to try and figure out what's going on. This may or may not be useful though, depending on precisely what is contained in the backtrace. – Lily Ballard Jul 07 '10 at 22:19
  • The stacks look identical, hence my surprise at your suggestion. My solution is to refactor the code so I can call the code in `viewDidAppear:` indirectly from `viewDidLoad:`, and so avoid the whole issue. I'm accepting your answer even though the double invocation is still a mystery to me. – iter Jul 07 '10 at 22:23
  • 17
    +1 for using the word idempotent. I've not fixed my duplicated viewDidAppear calls yet, but i've learned a new word! – Ted Mar 29 '12 at 11:15
  • Is it documented that they might not be balanced? – tcurdt Mar 27 '14 at 01:05
  • 1
    @tcurdt: I don't think so, but more importantly, there is no documentation saying that they are guaranteed to be balanced. And a poorly-written third-party VC can pretty easily leave them unbalanced. – Lily Ballard Mar 27 '14 at 16:40
1

maybe you invoke viewDidAppear in viewDidLoad (or some other stuff is going on there), since it's invoked only once during loading the view from the memory. It would match, that it's invoked two times only the first time.

Alistra
  • 5,177
  • 2
  • 30
  • 42
  • That was my first thought. But no, I'm not doing that. And if I set a breakpoint to see the on `viewDidAppear:`invocation, none of my own methods appear in it. – iter Jul 07 '10 at 21:55
1

This was not an iOS 5 bug, but a hidden behavior of addChildViewController:. I should file a radar for lack of documentation, I think https://github.com/defagos/CoconutKit/issues/4

1

If you have a line like this in your AppDelegate

window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];

make sure you DON'T have a "Main nib file base name" property in your plist set to "Window.xib" or whatever your custom window nib is named. If you do, remove that row from your plist and make sure you something like

yourRootVC = [[UIViewController alloc] init];
[window setRootViewController:yourRootVC];

in your AppDelegate after instantiating your window. In most cases, you could then safely delete the Window.xib as well.

whyoz
  • 5,168
  • 47
  • 53
0

Another solution that may have been your underlying cause: Be sure you are not presenting any new view controllers from within your viewWillAppear: method.

I was calling:

[appDel.window.rootViewController presentViewController:login animated:YES completion:nil];

from within viewWillAppear and seeing my originating VC's viewDidAppear: method called twice successively with the same stack trace as you mention. And no intermediary call to viewDidDisappear:

Moving presentViewController: to the originating VC's viewDidAppear: method cleared up the double-call issue, and so now the flow is:

  1. Original viewDidAppear: called
    • Call presentViewController here
  2. Original viewDidDisappear: called
  3. New view is presented and no longer gives me a warning about "unbalanced VC display"

Fixed with help from this answer: https://stackoverflow.com/a/13315360/1143123 while trying to resolve "Unbalanced calls to begin/end appearance transitions for ..."

Community
  • 1
  • 1
owenfi
  • 2,471
  • 1
  • 24
  • 37
0

You definitely should provide more info.

Is this the root view controller?
Maybe you initiate the navigation controller with this root view controller and then push it to the navigation controller once again?

Michael Kessler
  • 14,245
  • 13
  • 50
  • 64
  • Interesting idea, but no, I'm not pushing it twice. I `NSLog(@"%@", [[self navigationController] viewControllers]);` from `viewDidAppear` and see one instance of my view sitting on top of root view. – iter Jul 07 '10 at 22:01
  • Maybe you have 2 navigation controllers by mistake? – Michael Kessler Jul 07 '10 at 22:06
0

it's such an annoying problem, you'd think it runs once but then I now found out about this which is causing mayhem... This applies to all 3 (ViewDidAppear, ViewDidLoad, and ViewWillAppear), I am getting this when integrating with a payment terminal; once it finish calling the API, the window is being re-loaded when it's already on-screen and all it's memory is still there (not retained).

I resolved it by doing the following to all the routines mentioned above, below is a sample to one of them:

BOOL viewDidLoadProcessed = false;

-(void)viewDidLoad:(BOOL)animated
{
    if (!viewDidLoadProcessed)
    {
        viewDidLoadProcessed = YES;
        .
        .
        .
        ... do stuff here... 
        .
        .
    }
}

Repeat the above for all the other two, this prevents it from running twice. This never occurred before Steve Jobs died !!!

Kind Regards Heider Sati

Heider Sati
  • 2,476
  • 26
  • 28
-1

Adding [super viewDidAppear:animated]; worked for me:

//Called twice
- (void)viewDidAppear:(BOOL)animated{

}

//Called once
- (void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
}
Mohammad Zaid Pathan
  • 16,304
  • 7
  • 99
  • 130