8
votes

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:

  1. I have a UIView subclass, CustomView.
  2. I have a custom .xib file with three subviews
  3. 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.
  4. 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.
  5. I tried setting file owner to CustomView and connecting the IBOutlets to file's owner, but they were still nil.
  6. 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";
}
3
Do you check your nib file is in XCode bundle setting?Tirth
Did you have set xib's files owner property Class to CustomView And connect its view to your custom IBOutlet view.Anand Suthar

3 Answers

20
votes

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!

2
votes

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!)
}
1
votes

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.