I'm trying to create a custom UIView which holds references to its own IBOutlets. I then want to put this custom UIView into another nib.
I'm doing some additional logic in the custom UIView's awakeFromNib
method. Unfortunately, when I try to access the IBOutlets
in awakeFromNib
, they are nil.
Here's the setup:
- I have a
UIView
subclass, CustomView
.
- I have a custom
.xib
file with three subviews
- In the other nib (that belongs to the view controller), I have dragged a
UIView
onto the view, and then changed the custom class to CustomView
.
- I tried setting the view in the
CustomView
nib in IB to a custom class CustomView
and connecting the IBOutlets
to the view, but they were still nil.
- I tried setting file owner to
CustomView
and connecting the IBOutlets
to file's owner, but they were still nil.
- I also tried using another
IBOutlet UIView *view
and then adding that as a subview to self
in awakeFromNib
but that also didn't do anything.
Here's the code:
// CustomView.h
@interface CustomView : UIView
@property (nonatomic, retain) IBOutlet UITextField *textField;
@property (nonatomic, retain) IBOutlet UIView *subview1;
@property (nonatomic, retain) IBOutlet UIView *subview2;
// CustomView.m
@implementation CustomView
@synthesize textField, subview1, subview2;
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[[NSBundle mainBundle] loadNibNamed:@"CustomView" owner:self options:nil];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self setup];
}
- (void)setup {
// Fails because self.textField is nil
self.textField.text = @"foo";
}
I ended up using the steps in the most recent edit here and they worked beautifully.
You use a plain UIView
as the top level view in the xib
.
You then set file's owner to the custom subclass (CustomView
).
Finally, you add a line:
[self addSubview:[[[UINib nibWithNibName:@"CustomView" bundle:nil] instantiateWithOwner:self options:nil] objectAtIndex:0]];
in the if (self != nil)
block in both initWithCoder
and initWithFrame
.
Voila! The IBOutlets
are hooked up and ready to go after the call. Really pleased with the solution, but it was very difficult to dig up.
Hope this helps anyone else.
EDIT: I updated the link to one that isn't dead. Since I never spelled out the full code, here is what it looks like after modification:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
UIView *nib = [[[UINib nibWithNibName:@"CustomView" bundle:nil] instantiateWithOwner:self options:nil] objectAtIndex:0];
[self addSubview:nib];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
UIView *nib = [[[UINib nibWithNibName:@"CustomView" bundle:nil] instantiateWithOwner:self options:nil] objectAtIndex:0];
[self addSubview:nib];
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self setup];
}
- (void)setup {
// Doesn't fail because life is awesome
self.textField.text = @"foo";
}
This pattern has become so common that I actually created a category on UIView
called UIView+Nib
, which implements the following method:
+ (UIView *)viewWithNibName:(NSString *)nibName owner:(id)owner {
return [[[UINib nibWithNibName:nibName bundle:nil]
instantiateWithOwner:owner options:nil]
objectAtIndex:0];
}
So the above code can be simplified to:
[self addSubview:[UIView viewWithNibName:@"CustomView" owner:self]];
Note also that the above code can be refactored even more, since the logic is exactly the same in initWithFrame:
and initWithCoder:
. Hope that helps!
I am assuming the XIBs' structure is something like this
CustomView.xib
CustomView
UITextField -> linked to IBOutlet textField
other views
CustomViewController.xib
CustomView
If this is right, then your CustomView will be created but as it is not read from CustomView.xib it doesn't have any IBOutlets assigned.
However, if your CustomViewController.xib looks like following
CustomViewController.xib
CustomView
UITextField -> linked to IBOutlet textField of CustomView
then this should work. The IBOutlet of CustomView instance should be set by the CustomViewController.xib.
Better than setting any IBOutlets in the CustomViewController.xib would be to implement awakeAfterUsingCoder: in your CustomView and create a replacement object by loading your CustomView.xib in there. This way your CustomView remains truly custom and you don't have to edit other XIBs to change the structure, add/remove IBOutlets, etc.
As in Dr. Acula's answer, This is probably because custom view's nib is lazy loaded when loaded from another nib (Nested nib loading), so we need to instantiate it manually. In swift code will look like this :
override func awakeFromNib() {
super.awakeFromNib()
self.customview = UINib(nibName: "CustomViewNib", bundle: nil).instantiateWithOwner(self, options:nil)[0] as! UIView
self.customview?.frame = self.viewContainer.bounds
self.viewContainer.addSubview(self.customview!)
}