Creating a reusable UIView with xib (and loading from storyboard)

Ken Chatfield picture Ken Chatfield · Feb 20, 2014 · Viewed 62.6k times · Source

OK, there are dozens of posts on StackOverflow about this, but none are particularly clear on the solution. I'd like to create a custom UIView with an accompanying xib file. The requirements are:

  • No separate UIViewController – a completely self-contained class
  • Outlets in the class to allow me to set/get properties of the view

My current approach to doing this is:

  1. Override -(id)initWithFrame:

    -(id)initWithFrame:(CGRect)frame {
        self = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class])
                                              owner:self
                                            options:nil] objectAtIndex:0];
        self.frame = frame;
        return self;
    }
    
  2. Instantiate programmatically using -(id)initWithFrame: in my view controller

    MyCustomView *myCustomView = [[MyCustomView alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, self.view.bounds.size.height)];
    [self.view insertSubview:myCustomView atIndex:0];
    

This works fine (although never calling [super init] and simply setting the object using the contents of the loaded nib seems a bit suspect – there is advice here to add a subview in this case which also works fine). However, I'd like to be able to instantiate the view from the storyboard also. So I can:

  1. Place a UIView on a parent view in the storyboard
  2. Set its custom class to MyCustomView
  3. Override -(id)initWithCoder: – the code I've seen the most often fits a pattern such as the following:

    -(id)initWithCoder:(NSCoder *)aDecoder {
        self = [super initWithCoder:aDecoder];
        if (self) {
            [self initializeSubviews];
        }
        return self;
    }
    
    -(id)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            [self initializeSubviews];
        }
        return self;
    }
    
    -(void)initializeSubviews {
        typeof(view) view = [[[NSBundle mainBundle]
                             loadNibNamed:NSStringFromClass([self class])
                                    owner:self
                                  options:nil] objectAtIndex:0];
        [self addSubview:view];
    }
    

Of course, this doesn't work, as whether I use the approach above, or whether I instantiate programatically, both end up recursively calling -(id)initWithCoder: upon entering -(void)initializeSubviews and loading the nib from file.

Several other SO questions deal with this such as here, here, here and here. However, none of the answers given satisfactorily fixes the problem:

  • A common suggestion seems to be to embed the entire class in a UIViewController, and do the nib loading there, but this seems suboptimal to me as it requires adding another file just as a wrapper

Could anyone give advice on how to resolve this problem, and get working outlets in a custom UIView with minimum fuss/no thin controller wrapper? Or is there an alternative, cleaner way of doing things with minimum boilerplate code?

Answer

Fattie picture Fattie · Sep 18, 2014

Note that this QA (like many) is really just of historic interest.

Nowadays For years and years now in iOS everything's just a container view. Full tutorial here

(Indeed Apple finally added Storyboard References, some time ago now, making it far easier.)

Here's a typical storyboard with container views everywhere. Everything's a container view. It's just how you make apps.

enter image description here

(As a curiosity, KenC's answer shows exactly how, it used to be done to load an xib to a kind of wrapper view, since you can't really "assign to self".)