and loading NIB / XIB files? - iphone

I am a little curious, I have a view controller class and an NIB/XIB (both are named "MapViewController") If I do the following it loads the NIB with the matching name.
-(id)init {
self = [super initWithNibName:#"MapViewController" bundle:nil];
if(self) {
do things ...
}
return self;
}
if on the other hand I just specify [super init] does Xcode just look for a NIB that matches the name of the controller, is that how this is working?
-(id)init {
self = [super init];
if(self) {
do things ...
}
return self;
}
cheers Gary.

From the documentation:
If you specify nil for the nibName
parameter and do not override the
loadView method in your custom
subclass, the default view controller
behavior is to look for a nib file
whose name (without the .nib
extension) matches the name of your
view controller class. If it finds
one, the class name becomes the value
of the nibName property, which results
in the corresponding nib file being
associated with this view controller.

Yes, in this particular case it will work. According to the UIViewController, calling init is similar to calling initWithNibName:bundle: with nil as nib name:
If you specify nil for the nibName
parameter and do not override the
loadView method in your custom
subclass, the default view controller
behavior is to look for a nib file
whose name (without the .nib
extension) matches the name of your
view controller class. If it finds
one, the class name becomes the value
of the nibName property, which results
in the corresponding nib file being
associated with this view controller.

does Xcode just look for a NIB that
matches the name of the controller
Pretty much:
If you specify nil for the nibName
parameter and do not override the
loadView method in your custom
subclass, the default view controller
behavior is to look for a nib file
whose name (without the .nib
extension) matches the name of your
view controller class. If it finds
one, the class name becomes the value
of the nibName property, which results
in the corresponding nib file being
associated with this view controller.

Related

What does nibNameOrNil really mean?

The default init method signature on XCode-generated view controllers is:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil{ }
I've seen these initialized with both values supplied, just the nib name (with bundle as nil), or just nil as both. All seem to work.
How does the UIViewController really handle self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];? Is there a disadvantage to just passing in nil for both values?
If you pass nil as the nibName, the method will look for a nib with the same filename as your view controller.
For instance, if you have a view controller called MyViewController it will look for a MyViewController.xib nib file.
If no nib is found, you will need to override the loadView method to create and assign a UIView to the controller's view outlet.
- (void)loadView
{
UIView *theView = [[UIView alloc] ...
// Setup the main view
self.view = theView;
}
From the docs:
nibName:
If you specify nil for the nibName parameter and you do not override the loadView method, the view controller searches for a nib file using other means. See nibName.
nibBundle:
The bundle in which to search for the nib file. This method looks for the nib file in the bundle's language-specific project directories first, followed by the Resources directory. If nil, this method looks for the nib file in the main bundle.

Loading custom UIView from nib, all subviews contained in nib are nil?

I have a custom UIView object with a nib that defines the view/subview layout. My outlets are connected, and when I init the object from initWithFrame: everything is in order.
The problem I'm having is when I'm using IB to embed the UIView into another nib; the custom UIView appears, but none of the subviews it contains appear - in fact their symbols all resolve to nil. I have a very minimal initWithCoder: and awakeFromNib: implementation (just does logging) and my understanding is that as the nib is deserialized the subviews should at least be initialized during the process?
The only conclusion I'm coming to on my own is that one of two things is happening: either my outlet references are corrupt/bad and aren't working, or my understanding of load-from-nib process is faulty and I'm missing something.
Thanks in advance!
Edit: (Code posted for Nekto as requested... as you'll see, it does logging and thats it, heh.)
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
NSLog(#"ThumbnailGridView.initWithCoder frame= %f, %f", self.frame.size.width, self.frame.size.height);
}
return self;
}
- (void)awakeFromNib
{
NSLog(#"ThumbnailGridView:awakeFromNib");
}
Edit 2: Nibs, controllers, subviews, etc.
Nibs: I have a single nib containing a UIView. This UIView contains a single UIScrollView, which is filled with a grid of UIViews that are defined in another nib. (Note: this part works fine, as the fill is done programmatically and works with an initWithFrame: call.) The problem here is that the UIScrollView symbol is nil after initWithCoder: and awakeFromNib: are both called, so objects are just being added to nil and nothing happens. If the scrollview symbol was not nil, I'm sure this would work.
Controllers: There are two controllers that utilize this, one is done with initWithFrame: and works perfectly, the other embeds it as a nib-based reference. (As mentioned elsewhere here, defining a UIView in IB, setting the custom class.) I stepped through the code, and that view -is- being initialized properly - only its subviews are "missing".
Does this help give a clearer picture of the situation at all?
You may be misunderstanding how nib loading works. If you define a custom UIView and create a nib file to lay out its subviews you can't just add a UIView to another nib file, change the class name in IB to your custom class and expect the nib loading system to figure it out. You need to modify initWithCoder of your custom UIView class to programmatically load the nib that defines its subview layout. e.g.:
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
[[NSBundle mainBundle] loadNibNamed:#"CustomView" owner:self options:nil];
[self addSubview:self.toplevelSubView];
}
return self;
}
Your custom view's nib file needs to have the 'File's owner' class set to your custom view class and you need to have an outlet in your custom class called 'toplevelSubView' connected to a view in your custom view nib file that is acting as a container for all the subviews. Add additional outlets to your view class and connect up the subviews to 'File's owner' (your custom UIView).
Alternatively, just create your custom view programmatically and bypass IB.
Check that you are calling the 'super' implementation of initWithCoder: and awakeFromNib in each of your overridden methods i.e.
- (id)initWithCoder:(NSCoder *)decoder {
if ((self = [super initWithCoder:decoder])) {
...your init code...
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
...your init code...
}

Load NIB from variable

I'm trying to load a NIB based on a variable I get from my settings file. This is the code:
//select the right nib name
NSString *nibVar = [nibs objectForKey:#"controller"];
// create the view controller from the selected nib name
UIViewController *aController = [[UIViewController alloc] initWithNibName:nibVar bundle:nil];
aController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:aController animated:YES];
[aController release];
This unfortunately does not work.
Any ideas here?
Thanks
You cannot instantiate "UIViewController" with arbitrary NIBs, you have to instantiate "[whatever your custom view controller class is]" with the NIB for that class.
It's crashing because it's trying to access properties that don't exist in UIViewController.
If you want to do this kind of dynamic view-controller loading, you need to do a bit more work, and use the special Class class method that lets you instantiate an object using a string for the class name, instead of hard-coded.
Sometehing like:
Class viewControllerClass = NSClassFromString( nibVar );
UIViewController* aController = (UIViewController*) [[viewControllerClass alloc] initWithNibName:nibVar bundle:nil];
Make sure the NIB name is correct and does not include the xib extension. It is also case sensitive.

Question about initWithNibName

I make all my controllers in code and most of the GUI too. Some GUI's I make with IB. I then set the file's owner to the viewcontroller and drag an connection from the file's owner to the view. But initWithNibName confuses me...
I am override the designated initializer to this
- (id)init {
[super initWithNibName:nil bundle:nil];
return self;
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
return [self init];
}
Why do I not need to set which nib the viewController shall use in the init-initalizer? Because it works without. I thought I must use [super initWithNibName:#"SomeNib" bundle:nil];
In the init-initalizer
From docs: 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.
It works without only if your nib name is the same as your controller class name. In that case Apple does some magic. It is generally good form to specify the nib name.

A View Controller that can be instantiated both programmatically and in IB?

A view controller FooViewController needs to do some initialization when it's created (of itself, not of the managed view - before the view is even loaded).
I want to be able to create it in two ways:
Programmatically, possibly with some arguments:
FooViewController *fooViewController = [[[FooViewController alloc] initWithSomeData:data] autorelease];
[self.navigationController pushViewController:fooViewController animated:YES];
In Interface Builder.
For (1) I would write the initializers like this:
- (id)initWithSomeData:(Data *)data // designated initializer
{
if (self = [super initWithNibName:#"FooView" bundle:nil]) {
self.title = "Foo";
// perform initialization
}
return self;
}
- (id)init
{
return [self initWithSomeData:nilOrSomeDefaultValue];
}
// ... other initializers that call the designated initializer
(I hardcode the nib name since the controller always uses the same view configuration and because which view it uses doesn't concern the callers at all)
When created in Interface Builder, I would want the end result be the same as if the object was initialized with the parameterless init.
Now if I just do:
- (id)initWithCoder:(NSCoder *)decoder
{
return [self init];
}
The title, wantsFullScreenLayout and nibName properties which are set in the Inspector in Interface Builder would then be ignored, but that's OK. The nibName is hardcoded in init and title is set there in case the controller is instantiated programmatically anyways.
The problem is that parentViewController isn't set (the default initWithCoder: would set it based on the hierarchy of objects in the NIB).
How can I get the parent object from the nib? I would then change initWithCoder: to something like:
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [self init]) {
_parentViewController = [decoder parentObject];
}
return self;
}
Or should I be using some different approach to create controllers which can be instantiated both programmatically and in IB?
Don't, don't, don't try to make a viewcontroller that works with and without a nib. It will be a nightmare because nibloading uses some of the normal entry points and provides new ones, and it will be fragile with respect to OS revisions.
What you can do is it make the view controller always load from a nib, then give yourself a convenience initializer to go through the nib:
- (id) init {
return [[[self class] alloc] initWithNibNamed:#"MyNibName" bundle:nil];
}
Then you can reference it through other nibs the normal way, and just call the convenience init method when you don't want to explicitly deal with the nib.
Why not do init stuff in viewDidLoad - when creating outside of IB, you can set initial values with some other methods or properties after initialization, but before viewDidLoad is called.