Skip to content
 

Embedding custom-view Nibs in another Nib: Towards the holy grail

We have previously described how we load a custom view from a Nib. To recap:

  1. Build the UI for a custom view in a Nib,
  2. create an instance of the view, and finally
  3. add it as a subview, typically to a view controller’s view.

Steps 2 and 3 still require us to write code. This is perfectly fine in scenarios where the custom view’s surrounding view is itself instantiated and described programmatically.

It is much less satisfying if the surrounding view is described in a Nib file. In this case, the two lines of code are just for gluing together the custom-view Nib and the surrounding view. — Why can’t we just add instances of our custom view to the surrounding view’s Nib, just like we do with UIButtons and UISliders?

It turns out, we can.

Let’s consider the case for a custom control called SliderWithLabel, containing a UISlider and a UILabel. When the slider’s value changes, the custom control internally updates the label’s text and positions it above the slider’s thumb.

We want to design the SliderWithLabel control in a Nib, and then embed the SliderWithLabel control in some view controller’s Nib, say MyController.xib:

Embedding a custom-view Nib in another Nib

Step 1: Adding the view to the controller’s Nib

Add a UIView as a subview to a view in MyController.xib. In the Identity Inspector (⌥⌘3), change the Class attribute to the custom view’s name: SliderWithLabel.

Step 2: Realizing the problem, and restating what we really want

When the view controller’s Nib is loaded, an instance of SliderWithLabel is created by calling its -initWithCoder: method. The problem is that this will call the super implementation, i.e. result in an empty, standard UIView.

We basically want to tell the Nib-loading mechanism: “Hey, don’t initialize the object the usual way. Instead load the instance from this special SliderWithLabel.xib!”

Step 3: Finding some NSCoding magic in a hidden treasure chest

Amazingly, NSObject comes with the method -awakeAfterUsingCoder:, which you can override to return a different object (the custom view that we load from SliderWithLabel.xib!) than the object that was decoded (i.e. self).

- (id) awakeAfterUsingCoder:(NSCoder*)aDecoder {
    SliderWithLabel* theRealThing = (id) [SliderWithLabel loadInstanceOfViewFromNib];
    return theRealThing;
}

NSCoder, which is the class where the custom view object is instantiated from the Nib file, calls this method. If we load the “real” SliderWithLabel from its Nib by using our -loadInstanceOfViewFromNib method and return it instead of self, this is the view that gets inserted in its place.

Step 4: Painfully realizing that we can’t just always return the Nib-loaded object

If you try this, you will run into an endless recursive call to -initWithCoder:, because loading SliderWithLabel from its Nib will also call -awakeAfterUsingCoder:, which in turn will try to load SliderWithLabel from its Nib, and so on.

Therefore, the last crucial step is to know when SliderWithLabel is being instantiated by its own Nib – and when it is being instantiated by some other Nib (like MyController.xib). In other words, are we loading an empty “placeholder” or the real thing?

When we design our custom views in Interface Builder, it is usually because it consists of a number of subviews. So in the case of SliderWithLabel, we can judge whether it is a placeholder by checking if it has subviews:

if ([[self subviews] count] == 0) { ... }

This should now inject the custom view, as designed in SliderWithLabel.xib, into MyController’s view.

Step 5: Cool – but the frame, autoresizingMask, etc are ignored?

One last thing, though: Since we effectively discard the object that was initially loaded, we also lose the properties that we set on it in the view controller’s Nib, like frame, alpha, and autoresizingMask. We need to manually pass these properties through to the object loaded from the custom-view Nib.

So here is the full shebang:

- (id) awakeAfterUsingCoder:(NSCoder*)aDecoder {
    BOOL theThingThatGotLoadedWasJustAPlaceholder = ([[self subviews] count] == 0);
    if (theThingThatGotLoadedWasJustAPlaceholder) {
        SliderWithLabel* theRealThing = (id) [SliderWithLabel loadInstanceOfViewFromNib];

        // pass properties through
        theRealThing.frame = self.frame;
        theRealThing.autoresizingMask = self.autoresizingMask;
        theRealThing.alpha = self.alpha;
        // ...

        return theRealThing;
    }
    return self;
}

Discussion

Ever since starting iOS development back in early 2010, we have been building custom UI controls. We have gone through a plethora of different approaches, gradually moving from building and embedding the view programmatically to the design-in-Nib/embed-in-Nib approach that we have achieved with the technique outlined in this post.

Compared to the other options of designing and embedding a custom view, the “nested Nib” approach seems the most convenient. So for the time being, it has become our established best practice.

Alas, even best practices have minor drawbacks, notably:

  • Our Nib-loading mechanism assumes a single root view in the custom-view Nib.
  • A “placeholder” view is created (therefore taking up a bit of memory) before being replaced with the “real thing”.
  • We need to pass through all relevant properties from the “placeholder” object to the “real thing”.
  • We must be careful with -dealloc: it is called on the placeholder object right away when we swizzle it out for the Nib-loaded object, so we mustn’t to do anything here that shouldn’t be done more than once (e.g. removing yourself as a key-value observer).

Despite these quibbles, with this approach Nib-in-Nib-embedding is so easy to support that we have been using it extensively for the most diverse collection of custom UI controls. We’d be glad to hear about your experiences, and what you think about our little technique! Follow me on Twitter: @yangmeyer

UPDATE Apr 25, 2012: We are finally tackling the ARC migration. Using the described -awakeAfterUsingCoder: trick is easier than we previously thought: So ARC tells us not to assign self outside the init family? – turns out we don’t even need to set self at all, just return it. I updated the inline code listings. UPDATE July 9, 2012: I wrote up what you need to do under ARC in a followup post on my personal blog. I also published a demo on GitHub. Enjoy!

3 Comments

  1. Andrew says:

    I want to do something like this for my current project, but when I override awakeAfterUsingCoder:, I get an error saying I cannot assign to self outside of the init family of methods. It seems that this conflicts with the new ARC stuff somehow. Do you know if it is possible to move all of your implementation from awakeAfterUsingCoder: to initWithCoder:?

    Thanks.

    • Karsten (from the same team as Yang) says:

      ARC is announced for iOS 5. Currently it is unclear when it will be released. In fact it changes much of the behaviour in memory management as we currently know.
      If Apple decides to improve the interface builder with custom user-defined controls, all this discussion will be obsolete.

      The advantage in doing the self pointer change in awakeAfterUsingCoder: is that this method will be called independent of the implementation solution. It is not important if you instantiate the class by code via initWithFrame: or by nib via initWithCoder:. Another advantage is that all properties (like frame, autorizingmask, alpha, …) are already available for the developer.

      You are right, the best solution should be to instantiate the correct object instead of instantiating a placeholder and replace it later with the object from nib. But this is much more problematic. In fact we haven’t found a usable working solution. The best solution we found has a memory leak and use a static boolean/integer. Please see our question on stackoverflow: http://stackoverflow.com/questions/6357547/pluggable-custom-view-nibs-nib-in-a-nib-memory-leak-why

      If you find a solution for this issue we would be glad to read it.

    • Yang Meyer says:

      Thanks Andrew for your interesting question. Indeed we haven’t gotten round to delving into “the new ARC stuff” :-) yet. As Karsten clarified, we haven’t found a clean way to use -initWithCoder: instead of -awakeAfterUsingCoder: – take a look at the linked StackOverflow question, it sure feels hacky!

      The relevant passage from the ARC docs:

      It is undefined behavior, or at least dangerous, to cause an object to be deallocated during a message send to that object [This is precisely what we risk doing by calling [self release] in our -awakeAfterUsingCoder: —ed.]. To make this safe, self is implicitly const unless the method is in the init family.

      For the time being, I have no idea whether it would be safe (or even possible) to trick the compiler into considering the -awakeAfterUsingCoder: as belonging to the init family. Or perhaps we don’t need to release self and set it to the Nib-loaded object in the -awakeAfterUsingCoder:, but can maybe do that at a later point in the loading process…

      Let us know of any insights you find. Thanks!