iosxcodeinterface-builderxcode9.2

How to visualize reusable xibs in storyboards using IB_DESIGNABLE?


I want to visualize XIB's in a storyboard using the IB_DESIGNABLE constant in Xcode 9.2 with Objective C targeting iOS SDK 11.2.

According this article from Apple the prepareForInterfaceBuilder method is called by the Interface Builder to let you do whatever shall only be done for the Interface Builder.

When Interface Builder instantiates a class with the IB_DESIGNABLE attribute, it calls this method to let the resulting object know that it was created at design time. You can implement this method in your designable classes and use it to configure their design-time appearance.

Based on this article I want to visualize XIB's in my storyboard. When I use a UIView element on a ViewController and set

I get the following buildtime error:

Main.storyboard:
error: IB Designables: 
Failed to render and update auto layout status for MainViewController (8zi-kg-fjR):
The agent threw an exception.

What am I doing wrong that I get this error instead the visualized XIB in the View I use in the Main.storyboard?

This is the XibView.h

IB_DESIGNABLE
@interface XibView : UIView
@property (nonatomic, strong) IBOutlet UIView* contentView;
@property (nonatomic) IBInspectable NSString* nibName;
@end

This is the XibView.m

#import "XibView.h"

@implementation XibView

- (id)initWithCoder:(NSCoder *) coder
{
    self = [super initWithCoder: coder];
    if (self)
    {
        [self xibSetup];
    }
    return self;
}

//---------------------------------------------------------------------

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self xibSetup];
    }
    return self;
}

//---------------------------------------------------------------------

- (void)prepareForInterfaceBuilder
{
    [super prepareForInterfaceBuilder];
    [self xibSetup];
}

//---------------------------------------------------------------------

- (void)xibSetup
{
    [[NSBundle mainBundle] loadNibNamed:_nibName owner:nil options:nil];
    [self addSubview:self.contentView];
    self.contentView.frame = self.bounds;
}

@end

Solution

  • With the help of @E.Coms and still based on this article I figured it out what the problem was. To deliver an answer to be used for others here you can find the steps

    How to visualize reusable xibs in storyboards using IB_DESIGNABLE with Objective-C?

    1. Create a Xib holder objective-c header and class file (XibViewHolder)
    2. Create a Xib file (MyView.xib)
    3. Set the Xib's 'File's Owner' class to XibViewHolder
    4. On your storyboard on any view controller add a UIView
    5. Set that UIView's class to XibViewHolder class
    6. Set that UIView's Interface Builder property called Nib Name, which is defined in the Xib holder class, to the name of the Xib you want to load e.g.: MyView.

    This is the XibViewHolder.h

    IB_DESIGNABLE
    @interface XibViewHolder : UIView
    @property (nonatomic, strong) UIView* contentView;
    @property (nonatomic, strong) IBInspectable NSString* nibName;
    @end
    

    This is the XibViewHolder.m

    #import "XibViewHolder.h"
    
    @implementation XibViewHolder
    
    - (void)awakeFromNib
    {
        [super awakeFromNib];
        [self xibSetup];
    }
    
    - (void)prepareForInterfaceBuilder
    {
        [super prepareForInterfaceBuilder];
        [self xibSetup];
        [self.contentView prepareForInterfaceBuilder];
    }
    
    - (void)xibSetup
    {
        if (_nibName == nil) { return; }
        UIView* loadedNibView = [self loadViewFromNib];
        loadedNibView.frame = self.bounds;
        loadedNibView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
        [self addSubview:loadedNibView];
        self.contentView = loadedNibView;
    }
    
    - (UIView*)loadViewFromNib
    {
        NSBundle* bundle = [NSBundle bundleForClass:[self class]];
        UINib* nib = [UINib nibWithNibName:_nibName bundle:bundle];
        return [nib instantiateWithOwner:self options:nil].firstObject;
    }
    
    @end