I have my view controller class MyVC extending from UIViewController class. In the designated initializer I change the background color to GREEN as following
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
[self.view setBackgroundColor:[UIColor greenColor]];
}
return self;
}
I also have the loadView method that creates a new UIView object and changes its color to RED
- (void)loadView
{
UIView* view = [[UIView alloc]initWithFrame:[[UIScreen mainScreen] bounds]];
[view setBackgroundColor:[UIColor redColor]];
[self setView:view];
[view release];
}
The designated initializer is called before loadView call. So I expect that my view color (which I set GREEN in designated initializer) should become RED (which I did in loadView).
I see my color GREEN and if I comment that GREEN color line in designated initializer, then I see the RED color. So why is it not overriding the view properties in loadView method if it is called after initializer?
Caleb has it almost right. When you access a view controller's view property, the view accessor method checks whether the view has been loaded yet. If not, it calls loadView, then viewDidLoad, then returns the view.
This line in your initializer accesses the view property:
[self.view setBackgroundColor:[UIColor greenColor]];
So to return the view, the view accessor calls your loadView method. Your loadView method sets the view's background color to red. Then your initializer sets the background color to green.
If you sprinkle some NSLogs in your initializer and your loadView method, or if you put a breakpoint in your loadView method, you will see that loadView is called from view, which is called from initWithNibName:bundle:.
The purpose of -loadView is to, uh, load the view. It's called when you access the view controller's view property and the value of that property is nil. In this case, you're accessing self.view in your initializer, so that's when -loadView gets called. You set the view's background after that happens, so the view ends up with a green background.
Related
I have two view controllers in Xcode project (all view controllers are created in storyboard).
First view controller has two (or more) buttons with certain background images. Second view controller should display full-screen background image of certain button after user touch it (certain button).
Second view controller has a property UIImageView that should be allocated and initialized in the code of second view controller (UIImageView not created in storyboard).
Second view controller is a delegate for first view controller and has a method:
-(void) viewController:(ViewController *) viewController buttonPressed: (UIButton *) button.
Every button has a modal segue to second view controller.
So the sequence of actions of application is next (I realized that by debugging):
User touches any button
Button calls an action method in which delegate method viewController:buttonpressed: is called. UIImageView instance is allocated and initialized in this method with the image returned by button backgroundImageForState:
Than method viewDidLoad of second view controller is called, in which UIImageView instance should be added to super view of second view controller and displayed on screen.
The problem is that despite of allocation of UIImageView instance in delegate method viewController:buttonpressed:, that instance is become nil at the start of method viewDidLoad of second view controller. All actions that been made in viewController:buttonpressed: became unavailing.
The code is below:
First View Controller Code
- (void)viewDidLoad {
[super viewDidLoad];
ViewControllerForImage *temp = (ViewControllerForImage *) [self.storyboard instantiateViewControllerWithIdentifier:#"ViewControlForImage"];
self.delegate = temp;
}
-(IBAction)buttonPressed:(id)sender{
[self.delegate viewController:self buttonPressed:sender];
}
Second View Controller Code
#synthesize myImage;
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:myImage];
}
-(void) viewController:(ViewController *) viewController buttonPressed: (UIButton *) button{
myImage = [[UIImageView alloc] initWithImage:[button backgroundImageForState: UIControlStateNormal]];
[myImage setFrame:CGRectMake(0, 0, 320, 504)];
}
Why don't you just pass the information in a prepareForSegueMethod?
Also in your code why are you sending an instance of viewcontroller back to the second view controller? You are not using it at all.
Even though I am setting delegate and datasource, the data source methods are never being called.
I have a ViewController that adds a subview as such:
EVPhotoCollectionViewController *pc = [[EVPhotoCollectionViewController alloc] initWithNibName:#"EVPhotoCollectionViewController" bundle:nil];
self.damagePhotosView = pc.view;
Inside EVPhotoCollectionViewController I have delegate and datasource wired up in the xib, but also via code as follows:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.collectionView.delegate = self;
self.collectionView.dataSource = self;
[self.collectionView reloadData];
}
None of the datasource methods are ever called. I have verified self.collectionView is not null when it calls reloadData.
Thanks!
I think there are several things wrong here--and there are some complexities to view controller containment that you may need to read up on.
First off, you're not adding the EVPhotoCollectionViewController view as a subview of your vc, eg:
[self addSubView:pc.view];
Also, you're not setting a frame for the EVPhotoCollectionViewController, so depending on how it's implemented, it might not show up with the right size/position.
Lastly, it doesn't look like you're retaining the EVPhotoCollectionViewController anywhere. Its view will be retained by the view hierarchy, but it looks like the instance of EVPhotoCollectionViewController will be dealloc'd once the function creating it goes out of scope.
View controller containment: How does View Controller Containment work in iOS 5?
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.
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.