iOS Programming Recipe 6: Creating a custom UIView using a Nib

Creating a custom UIView using a Nib

Assumptions
  1. You are familiar with creating UIView subclasses, and instantiating UIView’s both from a Nib file or in code
  2. You are familiar with Nib files
Background

Sometimes you find yourself trying to create a quick composite UIView (UIView subclass w/ multiple subviews) where a UIViewController doesn’t seem necessary Please note that a UIViewController is the right choice most of the time. This can be a real pain to setup entirely in code if you have many subviews, and god forbid if you want to use auto layout! So you may find yourself wanting to use a nib to simplify things a bit, well this tutorial will go through the process of doing just that.

Getting Started
  • Create a new Xcode project based on the single view application template for iOS. This tutorial will assume you are using ARC, so you may want to make that selection when creating the new project.
  • Once you have created the new project a new UIView subclass to the project and name it CustomView.
  • Then create a new Nib file named CustomView.nib and add it to the project.
Setup the UIView Subclass (using a nib)
  • Open the newly created nib and add a UIView to it.
  • In the Attributes Inspector under the Simulated Metrics section, click the size drop-down menu and select none, this will allow you to resize the UIView to whatever size you like.
  • Resize the view to something like 200×300.
  • With the newly added UIView selected open the Identity Inspector and change the class name to CustomView matching the one you previously created.
  • Add a UILabel as a subview of the view and change the title to My Custom View. Then center it in the view, it should resemble the one shown below.

1

Creating a Convenience Initializer

Next we will create a convenience initializer in the CustomView class that will load the CustomView from the nib instead of creating it in code

    • Open CustomView.h and add the following class method definition.

+ (id)customView;
      • Next open CustomView.m and implement the class method as shown below (Please keep in mind this is a very basic implementation for our basic UIView subclass, you can alter it to your liking)

+ (id)customView
{
    CustomView *customView = [[[NSBundle mainBundle] loadNibNamed:@"CustomView" owner:nil options:nil] lastObject];

    // make sure customView is not nil or the wrong class!
    if ([customView isKindOfClass:[CustomView class]])
        return customView;
    else
        return nil;
}
Finishing The Demo App
      • Open ViewController.m and add the following viewDidLoad method, this will use our convenience initializer to create a CustomView and then we add it to our view hierarchy. You will also need to import CustomView.h in ViewController.m.

- (void)viewDidLoad
{
    [super viewDidLoad];

    CustomView *customView = [CustomView customView];
    [self.view addSubview:customView];
}
Code Explanation
      1. First we access the main bundle and load the nib we created.
      2. loadNibNamed:owner:options: returns an NSArray containing each of the top level objects in the nib. Since in our case we know there should only be one top level object (CustomView as we specified earlier) we can then call lastObject on the array. lastObject is used in order to safely access the array in case loadNibNamed:owner:options: failed. Note that lastObject returns nil if the array is empty.
      3. Finally we ensure that customView is actually a “CustomView” not some other class or nil.
That’s It!

As always if you have any suggestions for future recipes, or any questions or comments, please let us know!

Comments

  1. Mihai Damian says:

    One potential drawback with of approach is that you cannot directly link IBOutlets from the Nib since you have no file owner for it. Of course you could grab the subviews by tags and assign them yourself but this is error prone since it’s much more difficult to keep track of tags. Alternatively you could create an extra “template” instance of CustomView, set it as the file owner and then do the manual IBOutlet assignment from the template instance to the actual instance that you’ll be returning. This has the advantage of explicitly naming the UIViews you’re working with but it feels a bit hackish and it takes the most effort to implement.

    • Mike Turner says:

      Thanks for the comment!

      You can actually link up IBActions & IBOutlets, although it is slightly different than with a UIViewController. Using the example above add this property declaration to CustomView.h.

      //This will link to the label in CustomView.xib
       @property (nonatomic, strong) IBOutlet UILabel *label;

      Now in CustomView.xib, (control + drag) from the top level object (our CustomView object, where the property declaration lives, instead of file’s owner) to the UILabel. You should be presented with a HUD allowing you to select the “label” outlet just created!

      I’ll append the post to show this process.

      • Mihai Damian says:

        That’s a good point. Thanks for sharing this!

      • Joe Benton says:

        Hi, I can’t seem to get this bit right… where do you drag from/to. Do you drag from ‘Custom View’ in the sidebar to the label in the .xib file? When i try this it comes up with ‘trailing space to container’ along with other alignment bits.

        • Joe Hoffman says:

          Hi Joe,

          I assume this question is in reference to the comment. You’ll want to drag from the object browser over to the .h file line where the IBOutlet was defined.

    • Awesome and simple tutorial. Been searching for this for a long time! Thanks!

  2. Hi!

    As I’ve stumbled across aspects of this, that don’t rub me the right way, I’d like to leave two remarks:

    1. The convenience allocator is one of those rare occasions where one should totally throw an exception: That else-branch being hit means someone completely messed up the project setup — that’s a severe programmer error, and exceptions help find those fast. (Whereas the “graceful” failing by returning nil will cost some time finding out, why this darned view isn’t showing up…)
    2. Actually, why not cut the convenience allocator altogether, and leverage that UIView has a second designated initializer? The often overlooked “initWithCoder:” is used pretty much exclusively when a view is loaded from a UINib, so one can implement “initWithFrame:” along the lines of the convenience allocator you showed. That way, obtaining a fully configured instance, (when setting up views in code) remains natural. Together with a couple of sanity checks on the outlets, (preferrably in “awakeFromNib”) this holds even when the instance is created in *any* NIB (e.g. when the class is used in a view-controller scene of a storyboard).

    • Thanks for the comment!

      With respect to returning nil or throwing an exception when an error occurs while loading the nib, you’re probably right in most cases to throw an exception.

      As far as the convenience initializer, loading the object from the xib file results in initWithCoder: being called on the newly allocated object (which is where I would put any additional initialization code). After initialization, awakeFromNib is then called. If you are describing putting the loadNibNamed: call in initWithFrame, this will not work because initWithFrame: is an instance method, therefore we must have already allocated an instance of the class in order to call it. Calling loadNibNamed: creates that instance for you and then calls initWithCoder.

  3. Here is a cool link on loading nibs from a remote server http://blog.slaunchaman.com/2013/01/02/remote-nib-loading-for-fun-but-not-profit/

  4. Cheers for this! Exactly what I was looking for—quick, informative, and to the point!

  5. I spent hours trying to figure out different ways using init with coder, init with frame, etc. This is solid.

  6. Abdul Yasin says:

    Hi,
    I would like to present CustomView with transition.
    Could someone please guide me.

    Thanks

Speak Your Mind

*

css.php
Privacy Policy