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.
Related
I made a XIB that only consisted of a UITableView in IB. It was brought to my attention that it is possible to subclass UITableViewController and do away with the xib entirely.
My question is, how do you do this?
So far the only thing I have changed is my .h to be...
#interface MyView : UITableViewController
and removed my XIB. Obviously I get an error that states
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'Could not load NIB in bundle: 'NSBundle (loaded)' with <path>
name 'MyView''
so my question is, what else is left in order to accomplish this subclassing correctly?
There are two ways to set up your view controller in iOS.
With a .xib file: You instantiate the view controller with -initWithNibName:bundle:, passing the name of your .xib file as the first argument, and (unless you're doing some advanced stuff) nil as the second argument . The OS will look for your .xib file and unserialize it into a bunch of objects and attach them to your view controller. Then the OS calls your view controller subclass's -viewDidLoad: method, where you finish setting things up.
Programmatically: You instantiate the view controller with -init, or for a table view controller, initWithStyle:. The OS then calls your subclass's -loadView method, where you manually instantiate your view hierarchy.
It sounds like you just need to instantiate it with -initWithStyle: instead of -initWithNibName:bundle:.
If you had a ViewController without a .xib you could just create a tableview in the view did load or something.
UITableView *table = [[[UITableView alloc] initWithStyle:UITableViewStylePlain] autorelease];
table.dataSource = self;
table.delegate = self;
table.frame = CGRectMake(0, 10, self.view.frame.size.width, self.view.frame.size.height);
[self.view addSubview:table];
And then you just need to implement the proper delegate and data source methods like you would in a UITableViewController.
I created a new window based applications. Immediately i created a class as a subclass of ViewController and in the appdelegate didFinishLaunchingWithOptions method i've added the following code
TestView myView = [[TestView alloc] initWithNibName:nil bundle:nil];
[self.window makeKeyAndVisible];
[self.window addSubview:mainMenu.view];
[mainMenu release]
However, despite this it still initializes the nib. If i placed a button in TestView.xib it shouldn't technically show it right? I initialized TestView without a nib? Why does it show up?
If you pass nil to -initWithNibName:bundle:, it will attempt to load a nib from the main bundle that has a filename that matches the name of the subclass, or the name of the subclass without the "Controller" suffix.
If you dont want it from a nib you should not init it in this way, it will still search for a xib that matches the class name exactly.
From the apple documentation found here:
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html
If you specify nil for the nibName parameter, you must either override the loadView method and create your views there or you must provide a nib file in your bundle whose name (without the .nib extension) matches the name of your view controller class. (In this latter case, the class name becomes the name stored in the nibName property.) If you do none of these, the view controller will be unable to load its view.
I know there is a seemingly exact duplicate of this question here: iPhone SDK: what is the difference between loadView and viewDidLoad?
However, I have read that question and still it was not fully answered.
I'm not using IB as the UI is dynamic.
So should I create the self.view and then add the subviews in loadView,
or should I create the self.view in loadView and add the subviews in viewDidLoad?
When you load your view from a NIB and want to perform further customization after launch, use viewDidLoad.
If you want to create your view programatically (not using Interface Builder), use loadView.
For your specific question, you should add the subview in viewDidLoad. Because, if you overwrite the loadView, you have to do all the jobs, loading all the views.
Here is the explanation from Apple's documentation:
The steps that occur during the load cycle are as follows:
1.
* Some part of your application asks for the view in the view
controller’s view property.
2.
* If the view is not currently in memory, the view controller calls its loadView
method.
3.
* The loadView method does one of the following:
If you override this method, your implementation is
responsible for creating all
necessary views and assigning a
non-nil value to the view property.
If you do not override this method, the default implementation uses
the nibName and nibBundle properties of the view controller to try to load the view
from the specified nib file. If the
specified nib file is not found, it
looks for a nib file whose name
matches the name of the view
controller class and loads that file.
If no nib file is available, the method creates an empty UIView object
and assigns it to the view property.
4.
* The view controller calls its viewDidLoad method to perform any
additional load-time tasks.
It is very simple actually. If you do it without IB, then your UIViewController's view property is empty. So set it at loadView!
I only do setting of view at loadView and nothing else.
Other than that, do all thing inside viewDidLoad. Here is some example:
- (void)loadView {
CGRect frame = [[UIScreen mainScreen] applicationFrame];
baseView = [[UIView alloc] initWithFrame:frame];
[self setView:baseView];
[baseView release];
}
That's it! I am done. And would never want to add more to it. Then at the viewDidLoad, I add all those subviews I want to.
- (void)viewDidLoad {
[super viewDidLoad];
msg = [[UILabel alloc] initWithFrame:CGRectMake(0, 200, 320, 50)];
[msg setText:#"Your profile is empty!"];
[[self view] addSubview:msg]; // hey, I have done my view at loadView, so I have it now
[msg release];
}
I could be wrong in my understanding :)
loadView is the method that actually sets up your view (sets up all the outlets, including self.view).
viewDidLoad you can figure out by its name. It's a delegate method called after the view has been loaded (all the outlets have been set) that just notifies the controller that it can now start using the outlets.
viewDidLoad:
"This method is called after the view controller has loaded its associated views into memory. This method is called regardless of whether the views were stored in a nib file or created programmatically in the loadView method."
loadView:
"If you create your views manually, you must override this method and use it to create your views."
Add subviews in viewDidLoad. That way you are 100% sure than the view did indeed load and is ready for consumption.
Use viewDidLoad for initialize views and constrols. And use loadView if you don't have Nib/Xib and would like your ViewController has custom (not UIView) view.
Only use loadView when you want to create a view yourself.
Don't use loadView after you use interface builder or init with nib since these actions have already called loadView in the underly implementation.
Also, when use loadView, assign view first before doing any other settings:
-(void)loadView {
[super loadView];
// if you do any things here before assigning a view
// it will try to get a view first by calling loadView()
// and ends up with a crash since a dead loop.
self.view = ...;//assign your view here
//do other settings
}
I have created a custom UIView that I would like to use on multiple different view controllers in my iPhone application. Currently, I have copied the UIView that contains the controls from the nib file for the first view controller, pasted it into the other view controllers, and then wired up all of the actions to each view controller. This works fine, but is not the optimal way that I would like to use this custom view.
My custom view is reasonably simple, it consists of some UIButtons with labels, and tapping on these buttons fires actions that changes the contents of controls on my view controller's view.
What would be a strategy to consolidate the definition and usage of this UIView? Ideally, I would like to just reference this custom view from the nib of view controllers, and have my view controller respond to actions from this custom view.
EDIT: OK, based on J.Biard's suggestions, I have tried the following with not much luck.
I created another UIView based nib file with the contents (for now just some UIButton objects) of my reusable view and UIView subclass .m and .h files, and then set the nib File's Owner class to my newly created class name.
Then, I added most of the code from J.Biard (I changed the rect to 50,50,100,100, left out the setDelegate out for now, as I am just trying to get it working visually for now, and i found that [self.view addSubview:view] worked much better than [self addSubView:view]) to the end of the viewDidLoad method of the first view controller that is displayed when the app fires up.
And what I get now is my main view with a black square in it. Have I missed an outlet somewhere, or is there some initialization needed in initWithFrame or drawRect in the UIView subclass?
Create your MyCustomView class and nib file.
In the nib file, set Files Owner to MyCustomView - then design your view as normal, with a top level UIView. Create an IBOutlet in MyCustomView to link to your top level UIView in your nib file.
In MyCustomView add this method:
- (BOOL) loadMyNibFile {
if (![[NSBundle mainBundle] loadNibNamed:#"MyCustomView" owner:self options:nil]) {
return NO;
}
return YES;
}
In your view controllers you can use this custom view like so
- (void)viewDidLoad {
MyCustomView *customView = [[MyCustomView alloc] initWithFrame:CGRectMake(0, 0, 320, 44)];
[customView loadMyNibFile];
[self.view addSubview:customView.view]; //customView.view is the IBOutlet you created
[customView release];
}
You could also create a convenience class method on MyCustomView to do this if you liked.
If it is very simple I would recommend that you create a subclass of UIView in code and create instances of this class (or you can use Interface Builder to create the custom UIView that is then archived into the NIB file and restored later on also using code).
Using the code solution you could create instances of your custom UIView in your controller by calling something like:
#import "MyCustomView.h"
// let the superview decide how big it should be or set it as needed.
CGRect sizeRect = CGRectMake(0,0,0,0);
// create an instance of your view
MyCustomView *view = [MyCustomView alloc] initWithFrame:sizeRect];
// set a custom delegate on the view or set callback methods using #selector()...
[view setDelegate:self]; // self = view controller
// add the view to the controller somewhere... (eg: update CGRect above as needed)
[self addSubView:view];
// don't forget to release the view somewhere ;-)
This example assumes that you create a delegate protocol that your View Controller can respond to or you can wire up events dynamically using #selector. If you don't want to create instances of the view in code you could add a "UIView" to your NIB file and set it's class type in the inspector window (command -> 4 -> class dropdown).
If you want to do everything in interface builder you can create your custom UIView and use something like "- (NSArray *)loadNibNamed:(NSString *)name owner:(id)owner options:(NSDictionary *)options" (see NSBundle) to load the NIB file dynamically. This presents it's own challenges though it is also feasible.
The most involved option would be to create your own xcode custom UI library / plugin so that your custom control / view could be dragged into each NIB file from the Library window like any other control shipped by Apple.
Hope this clarifies or eliminates some options for re-using controls for you.
Cheers-
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.