2013-01-25

In iOS apps, we write Custom Views all the time. For example, CocoaControls has a nice collection of custom views.

However, it’s sometimes a tedious task: you typically have to implement drawRect: or build your view contents by hand, using -addSubview: repeatedly and setting UI properties in code. Not beautiful.

label = [[UILabel alloc] init];
        
label.font = [UIFont boldSystemFontOfSize:15.0];
label.textColor = [UIColor whiteColor];
label.shadowColor = [UIColor blackColor];
label.shadowOffset = CGSizeMake(0, 1);
label.baselineAdjustment = UIBaselineAdjustmentAlignCenters;
label.backgroundColor = [UIColor clearColor];
label.frame = CGRectMake(10,10,200,28);
label.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
        
[self addSubview:label];

And that’s just a simple, single-line label.

InterfaceBuilder.png

Same thing exactly. That’s better, right?

Now, there’s a handy tool that can quickly layout views, it’s Interface Builder. Problem is, while dynamically loading views stored in a nib file is not difficult per se, it still requires a few lines of plumbing, and it’s something I’ve rarely seen, even by experienced developers.

What if you could just write your custom view class (say, MyCustomView) and design its contents subviews in MyCustomView.nib? Even better, what if it loaded the subviews automatically?

Enter UIView+NibLoading

UIView+NibLoading is a UIView category that does just that. There’s also a concrete UIView subclass called NibLoadedView. Basically, all you have to do is subclass it, create you xib file, you’re done. NibLoadedView, in its initializers, loads its subviews from the nib file.

If you need to subclass another class (say, UIControl), you can use the UIView+NibLoading methods directly, and simply call [self loadContentsFromNib]. If you want even more control for example if you want several design variants, with as many nib files, you can specify the nib name and use [self loadContentsFromNibNamed:].

How does it work?

There’s something subtle going on: our custom view isn’t actually loaded from the nib file, only its subviews are. The custom view is being created elsewhere, from code (via a variant of [[UIView alloc] initWithFrame:]) or loaded from another nib (for example, the nib file of a UIViewController.)

However, we want to position the subviews properly, so we need a container view. Here’s the thing:

  • In the nib file, there can only be one root object, and it’s only used as a container,
  • the subviews are reparented from the container to the actual custom view,
  • this container is discarded after loading,
  • for this reason, it should be a UIView, not a member of your custom subclass.

Basically, -loadContentsFromNib loads a container view, and reparents the suviews to the receiver.

Moreover, the actual custom view is the file’s owner of the nib, so that you can define outlets from the custom view to its subviews.

Finally, here’s a small bonus: if the custom view being created has been created without a frame (more exactly, its frame is CGRectZero), it is resized to the size of the container view (the root view of the nib file). Otherwise, the subviews are properly resized.

Quick Demo

Let’s say I’m writing a “ColorView”, that shows a small color swatch, and its RGB value:

ColorView.png

Now all I have to do is write a small subclass of NibLoadedView:

@interface ColorView : NibLoadedView
@property (nonatomic) UIColor * color;
@end
	
@implementation ColorView
{
    IBOutlet UILabel * _codeLabel;
    IBOutlet UIView * _swatch;
}

- (void)setColor:(UIColor *)color_
{
    _color = color_;
    
    _swatch.backgroundColor = color_;

    CGFloat red, green, blue, alpha;
    [_color getRed:&red green:&green blue:&blue alpha:&alpha];
    _codeLabel.text = [NSString stringWithFormat:@"#%2X%2X%2X",(int)(red*0xFF), (int)(green*0xFF), (int)(blue*0xFF)];
}

@end

and design its contents subviews in ColorView.xib:

ColorView.xib.png

That’s all! I can now use instances of ColorView as usual, include them in other nib files, create them from code:

ColorViews.png

Wrap up

That’s all there is!

UIView+Nibloading is available on github, under a MIT licence.


Discussion

Comments are welcome, via nico@bou.io or on twitter.