Who should call viewDidLoad on programmatically loaded views? - iphone

When I need to load a view programmatically I do the following:
MyController* myController = [[MyController alloc] init];
[[NSBundle mainBundle] loadNibNamed:#"myNib" owner:myController options:nil];
// use my controller here, eg. push it in the nav controller
This works fine, but my controller's viewDidLoad is never called. So I resorted to manually calling it after the loadNibNamed call, but it doesn't seem correct. I was expecting the framework to call the viewDidLoad on my behalf. Is this the right way or I'm missing something?

I am new to Stack Overflow, but I discovered this question and discovered a couple other methods to load a nib file that ensure that the viewDidLoad method gets called automatically. Ed Marty's answer is correct, but it requires you to go outside the ViewController's code to load the nib file, these two examples I offer here allow you to keep your code inside the ViewController's implementation. Not necessarily better ways, just different. If you know of any drawbacks to doing it this way, please let me know your thoughts.
First, inside the initWithNibName:bundle: method of your UIViewController subclass, you can replace the call:
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
with something like this: self = [super initWithNibName:#"NameOfAlternateNibFile" bundle:nibBundleOrNil];
Or, you can accomplish what appears to do exactly the same thing by the following:
[[NSBundle mainBundle] loadNibNamed:#"NameOfAlternateNibFile" owner:self options:nil];
[self setView:self.view]; //this line ensures that the viewDidLoad method is called
The key is in understanding what is written in the comments above the function definition for $initWithNibName:bundle: in the UIViewController.h file (included at the bottom of my answer here, see italics).
The nice thing about doing this using either of these methods is that viewDidLoad gets called in either scenario.
Here are the directives listed in UIViewController.h:
The designated initializer. If you
subclass UIViewController, you must
call the super implementation of this
method, even if you aren't using a
NIB. (As a convenience, the default
init method will do this for you, and
specify nil for both of this methods
arguments.) In the specified NIB, the
File's Owner proxy should have its
class set to your view controller
subclass, with the view outlet
connected to the main view. If you
invoke this method with a nil nib
name, then this class' -loadView
method will attempt to load a NIB
whose name is the same as your view
controller's class. If no such NIB in
fact exists then you must either
call -setView: before -view is
invoked, or override the -loadView method to set up your views programatically.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil;

You should load view controllers with
MyController* myController = [[MyController alloc] initWithNibName:#"myNib" bundle:nil];
Make sure your MyController extends from UIViewController, and the View property is set properly in Interface Builder.

The views are loaded lazyly by UIViewController. If you use the accessor myController.view in your code, the view should be loaded and viewDidLoad be called.

I noticed the same thing. I think ViewDidLoad must be called by the view CONTROLLER. Since you don't have a view controllr in your nib, you have to call the viewdidload manually. I"m having to do the same thing.

Related

Custom initialise subview added from storyboard

I have a subclass of UIViewController that I want to add from the storyboard.
So I'm using what seems the standard methodology:
SubViewController *svc = [self.storyboard instantiateViewControllerWithIdentifier:#"SubViewControllerID"];
[self addChildViewController:svc];
[self.view addSubview:svc.view];
Which is fine but what if I want to call a custom init method on the subview?
I can do something like:
svc = [svc initWithFoo:#"Hello"];
Which seems to have to go after the addSubview call inorder for it to work.
Is this the best way to do this?
Seems a bit unorthodox. Calling an init method on an object that has already been created seems like its no longer truly an init method.
Maybe I should call it setWithFoo: or something and not have it return anything?
SubViewController *svc = [self.storyboard instantiateViewControllerWithIdentifier:#"SubViewControllerID"];
will cause the SubViewController to be inited with it's - (id)initWithCoder:(NSCoder *)decoder {} method.
Override that method (don't forget to call super)
If you want to do additional setup to your view controller after you instantiate it form the storyboard you can create some methods in the view controller's class and call them after the instantiate method fo the storyboard.
But be careful, if you try to make changes on any UI component in those methods, they wont be applied, and probably the app will crash. So use those methods to set params to the View Controller like array of objects, or any kind of data, and apply the UI changes for the view controller's view in viewDidLoad/viewWillAppear/viewDidAppear methods of your view controller.
Essentially I think the answer is that you can't use custom initialisers on ViewControllers added from the storyboard. Instead you have to set properties directly or through a method at the appropriate time in the life cycle as stated above.
Also as mentioned, the VC will be instantiated through initWithCoder, so calling an additional initialiser might be superfluous(?).
I encountered problems trying to use a custom initialiser that contains a call to super if I called it before the subview was added. I would just get a blank view added, I think because the superclass doesn't seem to know about the storyboard at that point. I had more success removing the call to super but that seems wrong.
This case would be more pertinent when adding subviews to a scrollview. For simplicity I left this out of my example.

Calling a single view in different UIViewControllers

I have declared a UIView inside a UIViewController class. Can I call this UIView in another UIViewController class?
If its possible how can I call it?
Yes, you can use a single instance of a view in a number of views/viewControllers. Typically I do the same with Views that carry advertisements.
You pass them along as you would do with any other object.
If you do not create it in Interface Bulder (I suggest creating it programmatically) then you may want to define it within your application delegate rather than a view controller and pass it to the individual view controllers that make use of it.
Within the view controller just add it as sub view accordingly, as you would do it with any other view too.
There is one thing though. When you add this view to another super view for the second time or more then it will be removed from its prior super view. That means that you will have to add it as super view again, when its prior super view becomes visible again. A view can only be part of one view hierarchy at a time.
Sample code, thanks to Gordon:
/* Untested and simplified */
AppDelegate.h:
#property ( strong, nonatomic) ReuseableView reuseableView
;
AppDelegate.m
#synthesize reuseableView;
/* in didFinishLaunchingWithOptions ...*/
reuseableView = [[alloc] init]; // or init from nib, initwithframe, etc.
viewController.m
/* In each view controller that uses the view */
- (void) viewWillAppear:(BOOL)animated
{
[self.view addSubview:((AppDelegate*)[UIApplication sharedApplication].delegate).reuseableView];
}
- (void) viewWillDisappear:(BOOL)animated
{
[((AppDelegate*)[UIApplication sharedApplication].delegate).ReuseableView removeFromSuperview];
}
I am not quite sure whether this removeFromSuperview is really required. The next addSubview will remove it from its existing superview anyway and if addSubview is called on the same super view twice in a row then it does not do any harm. However, it is save using removeFromSuperview at this point.
Well, summarized that is basically it. You define, create and store your shared view (the reusableView in Gordon's example) at a common place. The application delegate is a good place for that. In each view's controller that uses the shared view you fetch it from the delegate and add it as subview to the current view.
I would subclass UIView and import it on the ViewControllers where I wanna use it
NSArray *nibArray = [[NSBundle mainBundle] loadNibNamed:#"YourView" owner:self options:nil];
yourView = (YourView *)[nibArray objectAtIndex:0];
Then you set its frame and [self.view addSubview:yourView]

UIViewController ( init and initWithNibName )

Wish to know more about the practical differences between init and initWithNibName. SO answers such as this suggests that it is better to call "initWithNibName" indirectly through "init".
Are there any circumstances that we need to define "init" and
"initWithNibName" differently ?
Is it possible that any Nib file needs to be loaded more than once
during a single program execution ?
Are questions 1 & 2 inter-related ?
It is not better to call initWithNibName: indirectly through init. You just want to call initWithNibName: at some point. You can do that externally or internally. Some people think it's better to do it internally. I actually have a class called "LayoutUtil" that I keep layout-related helper methods to avoid writing tedious piece of layout-related code over and over. Here is my code to load a UIViewController:
+ (id)loadController:(Class)classType {
NSString *className = NSStringFromClass(classType);
UIViewController *controller = [[classType alloc] initWithNibName:className bundle:nil];
return controller;
}
And then I can just do:
MyViewController *c = [LayoutUtil loadController:[MyViewController class]];
If you want, you could add a method called ughhhh to a class and call it in there, it doesn't matter at all. The point is that it is not a better practice to call initWithNibName in the init method though, you just want to make sure you call it at some point when initiating a UIViewController.
- (id)ughhhh
{
self = [super initWithNibName:#"Myview" bundle:nil];
if (self != nil)
{
}
return self;
}
A nib file can definitely need to be loaded more than once. Everytime you call initWithNibName on a UIViewController the xib has to be loaded. A lot of people load UIViews that are not owned by a UIViewController like this:
[[NSBundle mainBundle] loadNibNamed:#"nameOfXIBFile" owner:self options:nil];
Everytime you call this function you will be loading the nib.
There are certain cases where a nib can be cached. An example would be a UITableView -- but the table view implements it's own cache. The operating system isn't doing any caching automatically.
init and initWithNibName: are related in that initWithNibName: automatically calls init on an object.
It's not ‘better to call "initWithNibName" indirectly through "init"’. You should use whichever one suits your needs better. Saying [[UIViewController alloc] init] is exactly like saying [[UIViewController alloc] initWithNibName:nil bundle:nil], so if those are the arguments you want to pass, you might as well use [[UIViewController alloc] init].
In answer to your questions:
You can define init and initWithNibName:bundle: differently if you want to. You can define just one of them. For example, UIImagePickerController only defines init, and if you try sending it initWithNibName:bundle:, it won't work properly. Or you can define some entirely different init... method. For example, UINavigationController only defines initWithRootViewController:. Whatever init methods you do define must eventually call one of its superclass's init methods.
Yes, a nib can be loaded multiple times. If you create multiple instances of the same view controller subclass, it's likely that you will load the same nib multiple times. In fact, it's possible for a single instance of a view controller to load its nib multiple times. How? If a view controller's view is not currently on screen, and the system runs low on memory, the system will ask the view controller to release its view. If the view controller later needs to put its view back on the screen, it will load the nib again.
Questions 1 and 2 are not related.
Answer of first question
Ideally every initWithNibName calls init internally so you do not need to define both in normal case Scenario , but you can if View is also loaded from loadView in one case and fron Nib as well in another case from different Views.
Answer of Second question
Everytime View is pushed in Stack Nib is loaded
Answer of Third question
No

UIViewController and XIB question

How do I prevent a UIVIewController from loading the xib?
I downloaded an XCode (iPhone) project online, and I want to stop the xib file from loading. The view did load method doesn't have any code in it which deals with xib. How do I force the UIViewController to be loaded from it's viewDidLoad method instead of the xib?
Overwrite loadview but DO NOT call [super loadview]. Example:
-(void)loadView{
self.view = [UIView new];
[self.view setBackgroundColor:[UIColor redColor]];
}
From the Documentation
loadView
Creates the view that the controller manages.
You should never call this method
directly. The view controller calls this method when the view property
is requested but is currently nil. If you create your views manually,
you must override this method and use it to create your views. If you
use Interface Builder to create your views and initialize the view
controller—that is, you initialize the view using the
initWithNibName:bundle: method, set the nibName and nibBundle
properties directly, or create both your views and view controller in
Interface Builder—then you must not override this method.
The default implementation of this method looks for valid nib
information and uses that information to load the associated nib file.
If no nib information is specified, the default implementation creates
a plain UIView object and makes it the main view.
If you override this method in order to create your views manually,
you should do so and assign the root view of your hierarchy to the
view property. (The views you create should be unique instances and
should not be shared with any other view controller object.) Your
custom implementation of this method should not call super.
If you want to perform any additional initialization of your views, do
so in the viewDidLoad method. In iOS 3.0 and later, you should also
override the viewDidUnload method to release any references to the
view or its contents.
Write your own init method. Later if you require the nib you can create a UINib object and when you need the view you can use instantiateWithOwner.
Using init when creating your view controller will prevent the nib loading. Another thing to do is to name the nib something other than the name of the view controller - because the nib can be loaded automatically if they match. I use ViewControllerName_iPad or ViewControllerName_iPhone and create the view required depending on device idiom.
The code to load from the xib file is not in the viewDidLoad method of the view controller itself.
You should usually find it in the application delegate's didFinishLaunchingWithOptions: method, or in the info.plist file, under the NSMainNibFile entry.
Try changing the initWithNibNameOrNil method to just init.
If that doesn't work, also override the loadView method by uncommenting it and setting your view there.
Here's an example:
- (id)init
{
self = [super init];
if (self) {
// Custom initialization
}
return self;
}
- (void)loadView {
[super loadView];
UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(0,0,320,460)];
myView.backgroundColor = [UIColor redColor];
self.view = myView;
[myView release];
}
Perhaps I'm barking up the wrong tree here, but is the UIViewController loading its view from a XIB because the XIB that loads the UIViewController itself has a "NIB Name" set in the view controller's settings?
e.g. load up MainWindow.xib, see your view controller. Select it, then look in the view controller settings over on the right (4th tab). One of the settings is "NIB Name". Just make that blank to stop the view controller loading its view from that XIB/NIB.

UIViewController -viewDidLoad not being called

Being new to Cocoa, I'm having a few issues with Interface Builder, UIViewController and friends.
I have a UIViewController subclass with a UIView defined in a xib, and with the controller's view outlet connected to the view. The xib's "file's owner" is set as myViewcontroller subclass.
In this one instance, the following code to load the controller/view (from the main view controller) doesn't work as expected:
if ( self.myViewController == nil )
{
self.myViewController = [[MyViewController alloc]
initWithNibName:#"MyViewController" bundle:nil];
}
[self.navigationController
pushViewController:self.myViewController animated:YES];
In MyViewController's methods, I have placed breakpoints and log messages to see what is going on:
-(id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
NSLog(#"initWithNibName\n");
}
return self;
}
-(void)viewDidLoad {
[super viewDidLoad];
NSLog(#"viewDidLoad\n");
}
Expected result
Both -initWithNibName and -viewDidLoad methods are called, and myViewController's view is displayed.
Observed result
Only -initWithNibName is called, the view is not displayed.
Have I missed something? Can anyone recommend anything to check? (Particularly in the wondrously opaque Interface Builder tool).
RE: SOLUTION FOUND!!!!!
Indeed that seems to be a working solution, however the real trick is not in setting the view.hidden property to NO, what makes the view load from the nib file is the calling of the UIViewController's view method, the view only actually gets loaded from the nib when the view method is called for the first time.
In that sense, a simple [viewController view] message would force the view to load from the nib file.
Ok, I have a partial answer - maybe the gurus can explain some more. The problem is:
[self.navigationController pushViewController:myViewController animated:YES];
Looking more closely, in this case self.navigationController is nil - so the push message is going no-where.
Instead, if I send:
[self.view addSubview:self.myViewController.view];
Then the view appears and -viewDidLoad is called.
I'm not entirely sure why self.navigationController is not set in this instance - the only thing I can think of is that self is a subclass of UIViewController rather than UITableViewController (where the pushViewController code came from).
Also, silently allowing messages to go to nil seems like a bad idea, although these answers say otherwise. See also my question here.
Final edit:
Answers in comments below, I've realised the display function that I was actually after (given myViewController is modal) is:
[self presentModalViewController:myViewController animated:YES];
Thanks everyone for their helpful responses.
SOLUTION FOUND!!!!!
Even something as innocuous as this makes the viewDidLoad method call happen.
Insert this right after alloc initWithNibName
viewController.view.hidden = NO; //calls viewDidLoad
make sure that the view outlet in File's Owner (your viewController subclass) is connected to the actual view (i.e. the 480X320 canvas you see on your screen that you use to build your UI)
Chances are that you might not have linked the supposed ViewController in main.storyboard from the Identity Inspector to the custom class you created. You might be able to navigate to that controller from other view controllers via segues but any of viewDidLoad(), viewWillAppear() etc. won't be executed.
Simply use
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
//Your Code here
}
instead of the viewDidLoad method.
Another reason, somewhat obvious in retrospect: if viewController.view is set in code, then the viewDidLoad event will not trigger.
It looks like a capitalization problem to me. You're referencing the class MyViewController instead of the property myViewController in the call to pushViewController.
Check your run log for errors. Almost certainly, the NIB is not loading, and there should be an error to that effect. The most likely cause for that is failure to put it in the bundle. Look in your "Copy Resources" build phase and make sure that the XIB is actually being copied. Build for the simulator, and go down into the build directory and make sure that the NIB is in the .app bundle.
Apart from other answers here,
It often happens when the identifier with which you instantiate your ViewController from the storyboard is incorrect. For e.g.
[[self getStoryboard] instantiateViewControllerWithIdentifier:MyVC];
If MyVC is the identifier of some other ViewController, this might happen.
OP is using nib instead of storyboard here. But the answer applies.
The page has been presented but not visible in Debug view hierarchy & in device(simulator also), issue happens based on and
i found the fix:
func viewWillLayoutSubviews{
if day == true{
self.view.backgroundColor = .clear
}else{
self.view.backgroundColor = .blue
}
}
Don't try to implement the self.view (viewcontrollers view) in function of layoutsubviews. So better use self.view in viewwillappear or viewdidload. This issue happens starts from v-14 devices.
Hope it works for you too.