AARON SMITHGithub | Work | Resume | Email
Here's a recipe I use for loading UIView instances from nibs without a UIViewController. This allows you to specify which object you want retained and autoreleased back to you by using the "tag" parameter in Interface Builder.
Here's the category I add to UIView:
UIViewNibLoading.h:
#import <UIKit/UIKit.h>
@interface UIView (UIViewNibLoading)
+ (id) loadViewNibNamed:(NSString *) nibName;
+ (id) loadViewNibNamed:(NSString *) nibName fromBundle:(NSBundle *) bundle retainingObjectWithTag:(NSUInteger) tag;
@end
And here's the implementation:
UIViewNibLoading.m
#import "UIViewNibLoading.h"
@implementation UIView (UIViewNibLoading)
+ (id) loadViewNibNamed:(NSString *) nibName {
return [UIView loadViewNibNamed:nibName fromBundle:[NSBundle mainBundle] retainingObjectWithTag:1];
}
+ (id) loadViewNibNamed:(NSString *) nibName fromBundle:(NSBundle *) bundle retainingObjectWithTag:(NSUInteger) tag {
NSArray * nib = [bundle loadNibNamed:nibName owner:nil options:nil];
if(!nib) return nil;
UIView * target = nil;
for(UIView * view in nib) {
if(view.tag == tag) {
target = [view retain];
break;
}
}
if(target && [target respondsToSelector:@selector(viewDidLoad)]) {
[target performSelector:@selector(viewDidLoad)];
}
return [target autorelease];
}
@end
The first method:
+ (id) loadViewNibNamed:(NSString *) nibName;
is very simple - this is a shortcut for the second method that does the real work; you don't have to specify all three parameters everytime you want to load a view nib.
The second method:
+ (id) loadViewNibNamed:(NSString *) nibName fromBundle:(NSBundle *) bundle retainingObjectWithTag:(NSUInteger) tag;
is slightly more complicated, but very easy to explain. The first line of that method:
NSArray * nib = [bundle loadNibNamed:nibName owner:nil options:nil];
is the standard way of loading a nib without any UIViewController instances. After it's loaded all root objects in the nib will be in the array. If we don't retain something, the entire array and root objects will be released. So we need to retain at least one object.
The problem with assuming the object we want is at index [0] is if you have more than one root object. It's not guaranteed to be at [0]. Interface builder lets us set a "tag" on any object. So let's use that to define which object we really want returned from this method.
Here's a screenshot of a custom view nib in Interface Builder with a tag setup:
Before we move further in code, it's worth mentioning "File's Owner." This isn't required, which is why it's nil in our code. Instead you can completely ignore it.
But how do we get IBOutlets available? By setting the Custom Class property in Xcode's property inspector - it'll make all of the IBOutlets you define on your custom class available.
Here's another screenshot of setting the custom control class on the view.
And another screenshot of IBOutlet references available for us to use:
And here's a header file of the custom control with those IBOutlets defined:
#import <UIKit/UIKit.h>
@interface CustomControl : UIView {
IBOutlet UIButton * myButton;
IBOutlet UILabel * myLabel;
}
@end
Moving back to the nib loading code, we come to a for loop:
UIView * target = nil;
for(UIView * view in nib) {
if(view.tag == tag) {
target = [view retain];
break;
}
}
This is self explanatory - it's searching for the view that's tagged correctly - so we know which view it is to retain and return to the caller. Once it's found, it's retained for safety, but will autorelease it before returned to the caller.
The next bit of code:
if(target && [target respondsToSelector:@selector(viewDidLoad)]) {
[target performSelector:@selector(viewDidLoad)];
}
is a bit of trickery to make your UIView subclasses behave like it would with a UIViewController - it calls viewDidLoad when it's loaded from a nib.
The last line:
return [target autorelease];
is a simple return with autorelease to ensure correct memory management.
Finally, here's how you'd use this new category:
@implementation GWViewController
- (void) viewDidLoad {
CustomControl * control = [UIView loadViewNibNamed:@"CustomControl"];
[self.view addSubview:control];
}
@end
Download the sample xcode project.
PS. I forgot to mention this same technique will work with NSView for Mac OS X projects.