viewDidLoad getting called twice on rootViewController at launch
Asked Answered
C

12

21

Anyone know why this root View Controller's viewDidLoad is being called twice at launch? It's driving me nuts!

here's the stack trace from first time through viewDidLoad:

#0  0x0000276a in -[RootViewController viewDidLoad] at RootViewController.m:71
#1  0x3097548f in -[UIViewController view]
#2  0x00002734 in -[RootViewController initWithCoder:] at RootViewController.m:39
#3  0x30ab5ce4 in -[UIClassSwapper initWithCoder:]
#4  0x30514636 in _decodeObjectBinary
#5  0x30514035 in _decodeObject
#6  0x30ab5a1d in -[UIRuntimeConnection initWithCoder:]
#7  0x30514636 in _decodeObjectBinary
#8  0x30515f27 in -[NSKeyedUnarchiver _decodeArrayOfObjectsForKey:]
#9  0x305163b0 in -[NSArray(NSArray) initWithCoder:]
#10 0x30514636 in _decodeObjectBinary
#11 0x30514035 in _decodeObject
#12 0x30ab4dde in -[UINib instantiateWithOptions:owner:loadingResourcesFromBundle:]
#13 0x30ab6eb3 in -[NSBundle(NSBundleAdditions) loadNibNamed:owner:options:]
#14 0x308f85f1 in -[UIApplication _loadMainNibFile]
#15 0x30901a15 in -[UIApplication _runWithURL:sourceBundleID:]
#16 0x308fef33 in -[UIApplication handleEvent:withNewEvent:]
#17 0x308fad82 in -[UIApplication sendEvent:]
#18 0x309013e1 in _UIApplicationHandleEvent
#19 0x32046375 in PurpleEventCallback
#20 0x30245560 in CFRunLoopRunSpecific
#21 0x30244628 in CFRunLoopRunInMode
#22 0x308f930d in -[UIApplication _run]
#23 0x309021ee in UIApplicationMain
#24 0x000022e4 in main at main.m:14

and the second time:

#0  0x0000276a in -[RootViewController viewDidLoad] at RootViewController.m:71
#1  0x30ab50cd in -[UINib instantiateWithOptions:owner:loadingResourcesFromBundle:]
#2  0x30ab6eb3 in -[NSBundle(NSBundleAdditions) loadNibNamed:owner:options:]
#3  0x308f85f1 in -[UIApplication _loadMainNibFile]
#4  0x30901a15 in -[UIApplication _runWithURL:sourceBundleID:]
#5  0x308fef33 in -[UIApplication handleEvent:withNewEvent:]
#6  0x308fad82 in -[UIApplication sendEvent:]
#7  0x309013e1 in _UIApplicationHandleEvent
#8  0x32046375 in PurpleEventCallback
#9  0x30245560 in CFRunLoopRunSpecific
#10 0x30244628 in CFRunLoopRunInMode
#11 0x308f930d in -[UIApplication _run]
#12 0x309021ee in UIApplicationMain
#13 0x000022e4 in main at main.m:14
Catling answered 3/7, 2009 at 23:31 Comment(0)
F
4

Weird. I haven't seen this particular case, but in general, you ought to assume that viewDidLoad can be called multiple times. It'll get called whenever a nib file that references that controller gets loaded.

For a simple app with only one nib, that shouldn't happen. But in a more-complex app that can load and unload view controllers, this happens all the time.

Fimbriate answered 3/7, 2009 at 23:41 Comment(8)
Yeah but what's happening is my objects are getting re-built and i'm getting two of each. I could but checks in to see if they're != nil but I haven't had to do this in previous apps. Something weird is going on here. Any ideas what might be calling it irregularly so I look deeper for the cause?Catling
In the second case, it's unarchiving an NSArray, which apparently has a reference to the RootViewController. Do you maybe have more than one reference to a RootViewController in the same nib file?Fimbriate
I agree, check to see if you have defined 2 instances of RootViewControllerSelhorst
While Mark is basically right, viewDidUnload and viewDidLoad are counterpart methods. At least it should be possible to assume that for every viewDidLoad call there will be another viewDidUnload call. This way you could do the tear down for the set up in viewDidLoad. When latter is called multiple times in sequence without a viewDidUnload in between things can go horribly wrong. This shouldn’t happen at all.Mellissamellitz
This is incorrect. viewDidUnload is only called in low memory situations. It is not guaranteed to be called for every call to viewDidLoad.Landry
So, not sure if commenting here is bad manner since I also posted an answer to this question, but @Landry is correct: viewDidUnload is not guaranteed to get called and only does in low memory occurrences. Additionally, viewDidLoad should not get called on the window's rootViewController multiple times. The controller was probably getting created twice: once by the main window nib, and once programmatically in the app delegate.Hogshead
In iOS 6, viewDidUnload is deprecated. From apple docs: "Views are no longer purged under low-memory conditions and so this method is never called.". developer.apple.com/documentation/uikit/uiviewcontroller/… So viewDidLoad will typically only be called once in the life of a view controller.Calv
I also think setting up subviews in viewDidLoad is better because you can lazily set up the subviews when it is about to show, e.g. changing UITabBarController selection.Calv
H
16

I had this same issue when my app was first launching. What I found was that in my MainWindow.xib file, I was setting both my App Delegate's viewController outlet, and my Window's rootViewController outlet to my root view controller. When you build a View Based project file in Xcode, your App Delegate's didFinishLaunchingWithOptions will be pre-populated with:

self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
return YES;

I believe that the self.viewController ivar is instantiated from MainWindow.xib before didFinishLaunchingWithOptions gets called. Then the pre-populated code above sets the Window's rootViewController. So if, in conjunction, you specify the rootViewController outlet for the Window in your MainWindow.xib file, your root view controller will actually be created twice and added as the Window's root view controller two times.

Hogshead answered 1/9, 2011 at 19:47 Comment(6)
This is exactly the problem that I was having.Spies
Same Problem. That was a sick deal. Thanks harrisonlee.Words
All that random bold makes this really hard to read.Ilion
just changed the bold to preBunyabunya
In my case, the VC init method is called once, but viewDidLoad is called twicePrettypretty
I am having a similar problem and have been trying to solve it for a week without success. But I'm using Swift and Storyboards, no xib files, no .h/.m Anyone have an idea as to how I could try this answer in Swift? #32288955Petrochemistry
S
8

I did some debugging and here's what I found about the ViewController loading order:

initWithNibName:bundle:     self = <original instance>, retainedOutlet = 0x0  
loadView >>>                self = <original instance>, retainedOutlet = 0x0  
      initWithCoder:        self = <coder instance>,    retainedOutlet = 0x0  
      initWithCoder:        self = <coder instance>,    retainedOutlet = 0x0  
      setView:              self = <original instance>, retainedOutlet = 0x0  
      setRetainedOutlet:    self = <original instance>, retainedOutlet = 0x1613c40  
      viewDidLoad           self = <coder instance>,    retainedOutlet = 0x0  
      awakeFromNib          self = <coder instance>,    retainedOutlet = 0x0  
loadView <<<  
viewDidLoad                 self = <original instance>, retainedOutlet = 0x1613c40  
viewWillAppear:             self = <original instance>, retainedOutlet = 0x1613c40  
dealloc                     self = <coder instance>,    retainedOutlet = 0x0
viewDidAppear:              self = <original instance>, retainedOutlet = 0x1613c40

During the loadView method, initWithCoder: is called and a new copy of the viewController is created. this is what is passed into a few of the methods (like viewDidLoad). the copy is destroyed later in a dealloc call. the good news is that in this copy, retained outlets are not configured, so you can use this as a test to know if you should initialize variables, call other methods, and most importantly, if you should release and destroy objects during dealloc.

Key takeaway: the real viewController will have its retained IBOutlet properties configured. if you are in an overridden method that is getting called multiple times, just check one of your retained IBOutlet properties for NULL. if they are NULL, then return immediately.

Anybody got any clues as to why this is happening this way?

Side effect of this: you can't use awakeFromNib reliably.

Style answered 5/2, 2010 at 17:44 Comment(0)
F
4

Weird. I haven't seen this particular case, but in general, you ought to assume that viewDidLoad can be called multiple times. It'll get called whenever a nib file that references that controller gets loaded.

For a simple app with only one nib, that shouldn't happen. But in a more-complex app that can load and unload view controllers, this happens all the time.

Fimbriate answered 3/7, 2009 at 23:41 Comment(8)
Yeah but what's happening is my objects are getting re-built and i'm getting two of each. I could but checks in to see if they're != nil but I haven't had to do this in previous apps. Something weird is going on here. Any ideas what might be calling it irregularly so I look deeper for the cause?Catling
In the second case, it's unarchiving an NSArray, which apparently has a reference to the RootViewController. Do you maybe have more than one reference to a RootViewController in the same nib file?Fimbriate
I agree, check to see if you have defined 2 instances of RootViewControllerSelhorst
While Mark is basically right, viewDidUnload and viewDidLoad are counterpart methods. At least it should be possible to assume that for every viewDidLoad call there will be another viewDidUnload call. This way you could do the tear down for the set up in viewDidLoad. When latter is called multiple times in sequence without a viewDidUnload in between things can go horribly wrong. This shouldn’t happen at all.Mellissamellitz
This is incorrect. viewDidUnload is only called in low memory situations. It is not guaranteed to be called for every call to viewDidLoad.Landry
So, not sure if commenting here is bad manner since I also posted an answer to this question, but @Landry is correct: viewDidUnload is not guaranteed to get called and only does in low memory occurrences. Additionally, viewDidLoad should not get called on the window's rootViewController multiple times. The controller was probably getting created twice: once by the main window nib, and once programmatically in the app delegate.Hogshead
In iOS 6, viewDidUnload is deprecated. From apple docs: "Views are no longer purged under low-memory conditions and so this method is never called.". developer.apple.com/documentation/uikit/uiviewcontroller/… So viewDidLoad will typically only be called once in the life of a view controller.Calv
I also think setting up subviews in viewDidLoad is better because you can lazily set up the subviews when it is about to show, e.g. changing UITabBarController selection.Calv
T
3

You can't assume viewDidLoad will be called only once. If you are initializing objects and want a guarantee do the initialization either in the init method or if you are loading from a nib file from the awakeFromNib method.

Towhead answered 4/7, 2009 at 1:2 Comment(0)
B
3

I had a similar problem and it was a result of renaming my XIB file and its ViewController class (File Owner). Don't do that -- as it really got the views and delegates misdefined inside the XML and it was not recoverable. Meanwhile, I had a reference to the load the original VC that was supposed to be my new VC. I believe that caused the parent to recreate itself and then the VC I was really tried to invoke. Basically, I created an indirect recursion to the VC that has x2 viewDidLoad entries in my trace.

I don't think there is any valid reason for x2 viewDidLoad as it is a genesis and can invoke other initialization with the wrong assumed pre-conditions. Every time I have seen the x2 viewDidLoad, it was a coding error on my part -- quite often when I was refactoring and moving VC classes around.

If there is a valid reason for more than on viewDidLoad call, please someone (Apple Dev are you listening) explain it in technical detail -- I have been searching for that answer for months now.

Broider answered 19/2, 2010 at 19:27 Comment(2)
Had same issue after renaming some files in the project. Ended up creating a new project.Slime
@Slime I think this is my issue as well. I am 99.9% sure that I do not have two segues or any other explanation for the double-load I am seeing. This was the one view controller where I remember playing around with the name and possibly deleting/recreating the file. Is there any way to get around creating an entirely new project? Copying the code into new files would be okay but I have a lot of work done in my storyboard file and would hate to have to re-create each VC's design/layout constraints, etc.Petrochemistry
P
3

I had this problem but was able to fix it.

Solution:

Rename the view controller class that is loading twice.

Details:

Rename it and make the new name something entirely new. Renaming the file doesn't stop the load-twice issue. Creating a new project (as suggested by others) might be overkill, at least try the simpler solutions first! Rename the class of the destination VC.

Hint: If renaming the class fixes your issue, you then obviously have to update all your references to that class. You can speed this up by using Command+Shift+F for project-wide find.

Petrochemistry answered 5/9, 2015 at 7:52 Comment(2)
I had a similar problem with one of my views. Renaming the view controller didn't work by itself. I had to delete and recreate the segue to the view. It must be a bug with XCode as I had not previously renamed or changed the storyboard.Kick
Really bizarre and thanks for this. If you see this message DaveG or others - was it the first one or the second one that was the real one? in my case it was the first one. I'm wondering if it is the same problem set.Spatterdash
Y
2

I ran into the same problem as I was redesigning a ViewController from scratch to get rid of the XIB file and to make the class reusable. I had this second ViewController instance which would receive a viewDidLoad message followed by a dealloc message.

I found out this was the result of the loadView method not being redefined in the ViewController. The default loadView called awakeFromNib, with the nibName property set to the class name. Even though I had removed the XIB file from the project, it was still in the application directory on the simulator.

So even though you could just reset the contents and settings of the simulator to get rid of the second viewDidLoad, a better way may be to just redefine loadView like this:

- (void)loadView {
    self.view = [[[UIView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame]] autorelease];
    self.view.autoresizingMask = UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleBottomMargin; 
}

It makes sense if you consider the documentation for the UIViewController's view property:

If you access this property and its value is currently nil, the view controller automatically calls the loadView method and returns the resulting view. The default loadView method attempts to load the view from the nib file associated with the view controller (if any). If your view controller does not have an associated nib file, you should override the loadView method and use it to create the root view and all of its subviews.

Yezd answered 9/2, 2011 at 15:10 Comment(0)
P
1

In my case, I didn't notice that I have actually assigned the rootViewController twice in:

application:didFinishLaunchingWithOptions: and applicationDidBecomeActive:

Postimpressionism answered 18/3, 2014 at 3:11 Comment(0)
H
1

Just to add to this, if you are using a system function, such as TouchID, then applicationWillResignActive in your AppDelegate will get invoked and if you are say, resetting controllers to a Secure Root Controller then you get reinvoked, and performSegueWithIdentifier(self.MAIN_SEGUE ,sender: self) will not fire!

Hyperpituitarism answered 18/2, 2016 at 7:43 Comment(1)
yep, using TouchID causes to call applicationWillResignActive and Activate again.Epencephalon
H
0

This happened to me when I merged a project from the storyboard to the old way using xibs for constructing views. The main reason for switching back was the fact I couldn't properly put up a modal view properly. The way I usually do it is by having a delegate method from a UIButton construct an instance of a certain viewcontroller, set some of its properties (the most import one being the delegate so i can properly dismiss the modal view controller again) and then present it in a modal way. In the new storyboard way, this is supposedly done with a segue. Customizing the transition is only doable by making a custom class that extends the UIStoryboardSegue class. I find this way too much hassle compared to the simple way it used to be so I merged back.

How did this cause me to have a viewcontroller load twice? When transferring the code from the storyboard project to the xib project, I made a couple of xibs (one for each ViewController) and copied the viewcontroller object from the storyboard. This led to a xib with in it not a viw, but a viewcontroller; meaning i had put a viewcontroller in a viewcontroller (since the file's owner is also an instance of the viewcontroller). I don't think in your case that you had this problem but I hope it maybe helps someone some day.

To fix this move the view from the view controller out of the view controller and to the root level of the objects section. Both the view controller and it's navigation item should be deleted. Build and run and you should see only one allocation for the view controller. This is the file owner.

Hightower answered 21/11, 2011 at 8:15 Comment(0)
S
0

What if your code did access the view property when it is not loaded yet, the view controller will create just empty view and it could trigger view did load accidentally.

Most common error is accessing view property during initialization. May be some property accessor(setter) that is invoked by xib should access view property accidentally.

What if some property is annotated with IBInspectable you should have to check isViewLoaded before apply some value to view.


-(void) setSomeProperty:(UIColor*) someColor
{
  _someColor = someColor;
  if(self.isViewLoaded) {
    // self.view causes view creation and invokes 'viewDidLoad' then the view is not ready yet.
    self.view.backgroundColor = someColor;
  }
}

-(void) viewDidLoad
{
  [super viewDidLoad]
  if(_someColor){
    self.view.backgroundColor = _someColor;
  }
}

Semicolon answered 6/8, 2019 at 0:52 Comment(0)
D
0

I did make something like this in SceneDelegate to check something and forget removing it. Then I find out that ends up calling viewDidLoad again.

func sceneDidBecomeActive(_ scene: UIScene) {
    ViewController().view.frame.origin.x = 100
}
Duster answered 17/9, 2020 at 7:7 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.