When working with views and view controllers in an iPhone app, can anyone explain the difference between loadView and viewDidLoad?
My personal context, is that I build all my views from code, I do not and will not use Interface Builder, should that make any difference.
I've found that often when I add init code to loadView, I end up with an infinite stack trace, so I typically do all my child-view building in viewDidLoad...but it's really unclear to me when each gets executed, and what is the more appropriate place to put init code. What would be perfect, is a simple diagram of the initialization calls.
Thanks!
I can guess what might be the problem here, because I've done it:
I've found that often when I add init code to loadView, I end up with an infinite stack trace
Don't read self.view in -loadView. Only set it, don't get it.
The self.view property accessor calls -loadView if the view isn't currently loaded. There's your infinite recursion.
The usual way to build the view programmatically in -loadView, as demonstrated in Apple's pre-Interface-Builder examples, is more like this:
UIView *view = [[UIView alloc] init...];
...
[view addSubview:whatever];
[view addSubview:whatever2];
...
self.view = view;
[view release];
And I don't blame you for not using IB. I've stuck with this method for all of Instapaper and find myself much more comfortable with it than dealing with IB's complexities, interface quirks, and unexpected behind-the-scenes behavior.
loadView is the method in UIViewController that will actually load up the view and assign it to the view property. This is also the location that a subclass of UIViewController would override if you wanted to programatically set up the view property.
viewDidLoad is the method that is called once the view has been loaded. This is called after loadView is called. It is a place where you can override and insert code that does further initial setup of the view once it has been loaded.
viewDidLoad()
is to be used when you load your view from a NIB and want to perform any customization after launch
LoadView()
is to be used when you want to create your view programmatically (without the use of Interface Builder)
Just adding some code examples to demonstrate what NilObject said:
- (void)loadView
{
// create and configure the table view
myTableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] applicationFrame] style:UITableViewStyleGrouped];
myTableView.delegate = self;
myTableView.dataSource = self;
myTableView.scrollEnabled = NO;
self.view = myTableView;
self.view.autoresizesSubviews = YES;
}
- (void)viewDidLoad
{
self.title = #"Create group";
// Right menu bar button is to Save
UIBarButtonItem *saveButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Save" style:UIBarButtonItemStyleDone target:self action:#selector(save)];
self.navigationItem.rightBarButtonItem = saveButtonItem;
[saveButtonItem release];
}
To prevent an infinite loop from happening when you read self.view, call the class' super implementation when you load a view. The super implementation will allocate a new UIView for you.
- (void) loadView {
[super loadview];
// init code here...
[self.view addSubView:mySubview1]; //etc..
}
The easiest way to use loadView is to make some type of base view controller, like MyBaseViewController which is subclass of UIViewController. In it's loadView method create view in this way:
-(void) loadView {
if ([self viewFromNib]) {
self.view = [self viewFromNib];
} else {
self.view = [[[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
}
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight;
self.view.backgroundColor = [UIColor whiteColor];
}
And when you need to make some view controller you just use subclass of MyBaseViewController and in it's loadView controller you just call [super loadView] like this
//sucblass loadView
-(void) loadView {
[super loadView];
//rest of code like this..
UILabel *myLabel = [[UILabel alloc] initWithFrame:myFrame];
[self.view addSubview:myLabel];
[myLabel release];
}
loadView() is called when your controller is asked to create its self.view. You can do it by yourself like
self.view = [UIView alloc] init...];
Or your controller's parent UIController class has already a method name -loadView() which initializes your self.view into blank view. Then you can call
[super loadView];
I really recommend the second approach as it encourages the inheritance. Only if your view controller is not directly inherited from UIViewController.
The definition given by Apple on viewDidLoad mentioned that it is called after the controller’s view is loaded into memory. To put it in a simple term, it is the first method that will load.
You might be thinking under what condition will this method being fully utilized? The answer is, basically whatever you wanted the app to load first. For instance, you might want a different background color, instead of white, you could perhaps choose blue.
Related
Hi everyone I've been debugging this issue for quite some time but no luck so far. I am quite lost here and have no clue on the reason causing this crash and how to fix it. I will be very grateful if anyone can offer me some help on this, thanks a lot!
I've prepared a sample project to demonstrate the issue at GitHub here.
The scenario is as the following:
There are two view controllers, namely the root view and modal view, each has a custom scroll view (class namely SubScorllView) as sub view, and the modal view has a button for dismissing the modal view.
The scroll views are sub-classes of UIScrollView, each with their corresponding delegate protocol, and their class hierarchy is as below:
UIScrollView
∟ SuperScrollView
.....∟ SubScrollView
The app launches and runs in a very simple manner, in AppDelegate's didFinishLaunchingWithOptions:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor blackColor];
RootViewController * rootVC = [[RootViewController alloc] init];
self.navVC = [[UINavigationController alloc] initWithRootViewController:rootVC];
self.navVC.navigationBarHidden = TRUE;
self.window.rootViewController = self.navVC;
[self.window makeKeyAndVisible];
ModalViewController *modalVC = [[ModalViewController alloc] init];
[self.navVC presentViewController:modalVC animated:YES completion:nil];
return YES;
}
And the views are loaded from xib files, which the scroll views' delegate are also set inside, and there are some overrides regarding the methods for initiating and setting delegate for the scroll view sub-classes.
The problem occurs when I dismiss the modal view through clicking the "Close" button in the modal view, when the button is clicked, the following happens:
- (IBAction)didPressedCloseButton:(id)sender {
self.subScrollView.delegate = nil;
[self dismissViewControllerAnimated:YES completion:nil];
}
And the app crashes at the following segment in SuperScrollView:
- (void)setDelegate:(id<SuperScrollViewDelegate>)delegate {
_superScrollViewDelegate = delegate;
// trigger UIScrollView to re-examine delegate for selectors it responds
super.delegate = nil;
super.delegate = self; // app crashes at this line
}
With the following error message in the console:
objc[6745]: Cannot form weak reference to instance (0x7fa803839000) of
class SubScrollView. It is possible that this object was
over-released, or is in the process of deallocation.
I don't understand why the app would crash and giving the above error message, or how should I fix it. I tried to search with the error message but seems that message is mostly related to other classes like text views, while some others solved it with setting the delegate of the scroll view to nil before deallocating but it doesn't work in my case.
==========
Update: Just tested if this happens on iOS 8 with simulator, it doesn't crash like on iOS 9 at all.
When the SuperScrollView is deallocated, setDelegate is called implicitly. In iOS 9, you cannot set the delegate to self, because self is in the process of being deallocated (no idea why this worked in iOS 8). To work around this issue, you can check first to see if the passed in delegate parameter is not nil, and only then set super.delegate to self:
- (void)setDelegate:(id<SuperScrollViewDelegate>)delegate {
_superScrollViewDelegate = delegate;
// trigger UIScrollView to re-examine delegate for selectors it responds
super.delegate = nil;
if(delegate)
{
super.delegate = self;
}
}
If for some reason you need to support self responding to the UIScrollView delegate methods even when _superScrollViewDelegate is nil, you could create a parameter
#interface SuperScrollView ()
#property (nonatomic, weak) SuperScrollView * weakSelf;
#end
at the top of the file, and set it in setup
- (void)setup {
super.delegate = self;
self.weakSelf = self;
}
Then, in setDelegate, just check that weakSelf is not nil. If weakSelf is nil, then self is in the process of deallocating and you should not set it to the super.delegate:
- (void)setDelegate:(id<SuperScrollViewDelegate>)delegate {
_superScrollViewDelegate = delegate;
// trigger UIScrollView to re-examine delegate for selectors it responds
super.delegate = nil;
if(self.weakSelf)
{
super.delegate = self;
}
}
super.delegate = self ,super here is UIScrollView, super.delegate is of type UIScrollViewDelegate, and self is of type UIScrollView, so you are setting UIScrollView's delegate to be a scroll view, which doesn't make sense, normally controller should be the delegate of UIScrollView.
When you dismiss modal view controller, it is in the process of deallocation. super.delegate = self;, here self is a scroll view which is a subview of self.view, which belongs to the modal view controller. so self is also deallocating.
I had the same problem in Swift and cncool's answer helped me.
The following (considering being in the parent class's instance) fixed my issue:
deinit {
self.scrollView.delegate = nil
}
I wanted to know how to make a viewcontroller without the use of xib or storyboard. So in this case, I would be getting the viewcontroller as such
PopupViewController* vc = [[PopupViewController alloc] init];
[self addChildViewController:vc];
[self.view addSubview:vc.view];
[vc didMoveToParentViewController:self];
I know it has to do something with overriding the init method since we are not using initWithCoder ?
I know I have to create a view and set it as the self.view for the PopupViewController, but I was wondering how I could do that.
EDIT: Yes it may be much easier just to make an xib file or add a view controller to the storyboard, this is to understand deeply how view controllers work.
The best place to init the view is in PopupViewController's loadView method. Something like:
- (void)loadView {
self.view = [MyViewClass new];
}
Then, in MyViewClass initWithFrame: method build all subviews and set constraints to it. If you're not using constraints, override layoutSubviews method.
I am using something like:
VC = [[SettingsViewController alloc] initWithNibName:nil bundle:nil];
viewDidLoad is not called yet.
But when I do:
VC.view.frame = CGRectMake(...);
At this point viewDidLoad is called.
But the issue is, that the view dimensions that I am passing in the above code statement is not used in the viewDidLoad method.
I think it sees that view is being used, so it is time to load the view, and after loading the view it must be assigning the frame dimensions to the view. But what if I want that view dimensions set before viewDidLoad gets called, so that I can use those dimensions in the viewDidLoad method..
Something like initWithFrame..
Also, I don't have the view dimensions in the view controller. I have to assign the view dimensions from outside of the VC.
So probably after calling initWithNibName:bundle: method I can save the view frame dimensions in some variable.. but that doesn't look like a clean solution, does it?
viewDidLoad is called when the view did load. (surprise)
so by the time you call VC.view, before it return, the viewDidLoaded will be executed and then the view is returned, and set the frame.
so from your current approach, it is not possible
anyway, why you need view frame in viewDidLoad? maybe you can move that part into viewWillAppear / viewDidAppear which is only get called when the view is about to present
You can do something like this:
In the interface
#interface SettingsViewController : ... {
CGRect _initialFrame;
}
...
- (id)initWithFrame:(CGRect)frame;
#end
In the implementation
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithNibName:nil bundle:nil];
if (self) {
_initialFrame = frame;
}
return self;
}
- (void)viewDidLoad
{
self.view.frame = _initialFrame;
[super viewDidLoad];
}
and then from the class you use these controller:
VC = [[SettingsViewController alloc] initWithFrame:CGRectMake(...)];
I know that viewDidLoad method may be called multiple times during UIViewController's lifecycle. But how is that possible? How to make it called more than once not calling it directly? I tried doing it this way:
UIView *view = [[UIView alloc] initWithFrame:self.view.frame];
view.backgroundColor = [UIColor greenColor];
self.view = view;
and whereas my view is actually changed, viewDidLoad is not called. Can anyone give an example?
The first time you access a viewController's view property the view will be created with loadView and then you will receive the viewDidLoad call.
You will not receive the viewDidLoad call again unless the view is destroyed - this may occur if your viewController goes off screen and UIKit decides to purge any view's that are not visible. Thus next time you access the view property it will notice it does not exist and again create one with loadView and then call viewDidLoad.
viewWillAppear method is an UIViewController method. Why you shouldn't call directly?
By the way there is no way to do that, while you assign an UIView to your self.view, id you are not doing it in the init or in the loadView or didLoad methods..
the life cycle is that:
init
loadView //change your view here
viewDidLoad
Then you present the view and:
viewWillAppear:
viewDidAppear:
if you want to change the view during your uiviewcontroller life cycle you should do:
UIView *view = [[UIView alloc] initWithFrame:self.view.frame];
view.backgroundColor = [UIColor greenColor];
[self viewWillAppear:NO]; //set to yes if you are making some kind of animation
self.view = view;
[self viewDidAppear:NO];
The will disappear and did disappear will be called according to the UIVIewController life cycle.
I have a UIView, for which I implemented the init method. This works for me because I always initialize my UIViews with the simple code below.
MYView *view = [[MYView alloc] init];
[self.view addSubview:view];
[view setFrame:CGRectMake(0,0,99,99)];
QUESTION: Can this view be used with storyboards? or do I need to implement other "init" methods? Also, what happens if someone instantiates the view with initWithFrame? Do I need to implement all the "init" methods to handle all these cases?
Storyboard and XIB files will use the initWithCoder: methods.