83

I typically like to create and design my uiviews in interface builder. Sometimes I need to create a single view in a xib that can be reused in multiple view controllers in a storyboard.

Garfbargle
  • 3,222
  • 2
  • 17
  • 17

7 Answers7

136

Reuse and render a xib in a storyboard.

Tested with Swift 2.2 & Xcode 7.3.1

1 ---- Create a new UIView named 'DesignableXibView'

  • File > New > File > Source > Cocoa Touch Class > UIView

2 ---- Create a matching xib file named 'DesignableXibView'

  • File > New > File > User Interface > View

3 ---- Set the file owner of the of the xib

  1. select the xib
  2. select file's owner
  3. set custom class to 'DesignableXibView' in the Identity Inspector.

Setting a Xib's Owner to a Custom Class

  • Note: Do not set the custom class of the view on the xib. Only the File Owner!

4 ---- DesignableXibView's Implementation

//  DesignableXibView.swift

import UIKit

@IBDesignable

class DesignableXibView: UIView {

    var contentView : UIView?

    override init(frame: CGRect) {
        super.init(frame: frame)
        xibSetup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        xibSetup()
    }

    func xibSetup() {
        contentView = loadViewFromNib()

        // use bounds not frame or it'll be offset
        contentView!.frame = bounds

        // Make the view stretch with containing view
        contentView!.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]

        // Adding custom subview on top of our view (over any custom drawing > see note below)
        addSubview(contentView!)
    }

    func loadViewFromNib() -> UIView! {

        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView

        return view
    }

}

5 ---- Test your reuseable view in a storyboard

  1. Open your storyboard
  2. Add a view
  3. Set that view's Custom Class
  4. wait a sec ... BOOM!!

Xib Rendering Inside a Storyboard View Controller

Garfbargle
  • 3,222
  • 2
  • 17
  • 17
  • 1
    Thanks! You may want to add an example using constraints with Swift, as you did in your Obj-C answer (with and without VFL). – Evan R Jun 10 '16 at 01:39
  • Hey @EvanR! Unfortunately I ran into some issues with trying to use autolayout constraints that I was not able to figure out how to overcome. It seems that Xcode was not able to do the live rendering properly. – Garfbargle Jun 10 '16 at 18:10
  • 1
    I was able to get it working with constraints, although didn't test with live rendering. I added `CenterX`, `CenterY`, `Width`, and `Height` constraints and it worked fine. I can email you sample code or post an answer on here. – Evan R Jun 10 '16 at 19:15
  • 1
    In my xib, I've got some outlets and such to a couple of views and buttons. The code fails in xibSetup() at the line contentView = loadFromNib(), it fails due to "this class is not key value coding-compliant for the key ". I tried making contentView my view class, but that still didn't work. – SuperDuperTango Jun 12 '16 at 07:17
  • 3
    I answered my own question, it looks like I setup the outlets BEFORE i read these SO articles saying to set the File Owner, not the view. It worked after I disconnected all of the outlets, ensured the file owner was correct, and then re-added all of the outlets. – SuperDuperTango Jun 12 '16 at 08:23
  • how we can add the ibout let for th ui controls in the customes view to be change it's behavior /adding target from the View controller – Amr Angry Aug 24 '16 at 16:27
  • 1
    Thanks for this! Very handy. Any idea why the custom view isn't appearing in the Interface Builder Preview? – Rogare Aug 27 '16 at 15:14
  • 1
    what is the reason to adding class file to file owner not in uiview ? – Kishore Kumar Sep 30 '16 at 07:00
  • @Garfbargle How to initialize this view programmatically in a ViewController? – Burhanuddin Sunelwala Nov 08 '16 at 08:53
  • 4
    What's the difference between setting file owner vs custom class? – Paweł Brewczynski Jan 25 '17 at 09:56
  • I wish someone would explain why it has to be the file owner instead of the view – Mazen Apr 16 '17 at 09:03
  • @Rogare Make sure you have `@IBDesignable` just before your class declaration, e.g. `@IBDesignable class DesignableXibView { ...`. – nyg Sep 14 '17 at 11:29
  • How would you link @IBAction to a shared UIView? from Xib I mean – Idrees Ashraf Nov 11 '17 at 18:09
  • 9
    I don't see the preview in the interface builder either. Xcode 9 – Jaap Weijland Nov 12 '17 at 21:16
  • 3
    The code works when running but in my Xcode 9 it shows 'Designable build failed' under the module line – ManuQiao May 16 '18 at 01:15
  • If the XIB contains any UICollectionView it's not rendering in the storyboard. Any idea? – Soumen Jun 04 '18 at 08:16
  • For anyone trying to adapt this answer for Objective-C. I was facing issues with live rendering when converting to Objective-C, but finally succeeded. See my answer bellow https://stackoverflow.com/a/55242994/4894980 – AnthoPak Mar 19 '19 at 14:12
  • Works perfectly in 2020 with Swift 5 and Xcode 12.1 – Teetz Oct 30 '20 at 14:08
76

NEW! updated answer with ability to render directly in the storyboard (and swift!)

Works in Xcode 6.3.1

Create a new UIView named 'ReuseableView'

  • File > New > File > Source > Cocoa Touch Class > UIView

Create a matching xib file named 'ReuseableView'

  • File > New > File > User Interface > View

Set the file owner of the of the xib

  1. select the xib
  2. select file's owner
  3. set custom class to 'ReusableView' in the Identity Inspector. enter image description here

    • Note: Do not set the custom class of the view on the xib. Only the File Owner!

Make an outlet from the view in the ReuseableView.xib to your ReuseableView.h interface

  1. Open Assistant Editor
  2. Control + Drag from the view to your interface

enter image description here

Add initWithCoder implementation to load view and add as a subview.

- (id)initWithCoder:(NSCoder *)aDecoder{
    self = [super initWithCoder:aDecoder];
    if (self) {

        // 1. load the interface
        [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
        // 2. add as subview
        [self addSubview:self.view];
        // 3. allow for autolayout
        self.view.translatesAutoresizingMaskIntoConstraints = NO;
        // 4. add constraints to span entire view
        [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[view]|" options:0 metrics:nil views:@{@"view":self.view}]];
        [self addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[view]|" options:0 metrics:nil views:@{@"view":self.view}]];

    }
    return self;
}

enter image description here

Test your reuseable view in a storyboard

  1. Open your storyboard
  2. Add a view
  3. Set that view's Custom Class

enter image description here

Run and observe! enter image description here

Community
  • 1
  • 1
Garfbargle
  • 3,222
  • 2
  • 17
  • 17
  • @Garfbargle Adding on top of this... is it possible to display in the storyboard the subelements of the xib? In your last step, the added view appears like a gray square, is any way to fix it? Thx! – Andres C Feb 23 '16 at 18:48
  • 2
    @andres.cianio Yes however you have to take a completely different approach. You have to programmatically build the view and use the IBDesignable directive. This was specifically for people that wanted to build views visually though. I am not aware of a way to build a view in an xib visually and have it be rendered in a storyboard. I would not be surprised if this will be possible very soon though if it is not already. – Garfbargle Feb 24 '16 at 19:02
  • @Garfbargle thanks... This has saved my life, but having it render in the SB when inserting the view would have been beautiful ;) – Andres C Feb 24 '16 at 19:45
  • 2
    Great explanation! But I have a hint for you: Use `UINib(nibName: nibName, bundle: nil).instantiateWithOwner(nil, options: nil)` which is faster than the NSBundle-Version. – blackjacx Mar 11 '16 at 20:43
  • @Garfbargle This seems to satisfy the requirement that you can design in IB and have it rendered in the storyboard. Basically you do exactly what you did for the XIB and put `@IBDesignable on the class.` You have to implement `init(frame:)` as well - but other than that it works pretty well! http://supereasyapps.com/blog/2014/12/15/create-an-ibdesignable-uiview-subclass-with-code-from-an-xib-file-in-xcode-6 – wyu May 19 '16 at 23:52
  • @Garfbargle, Aren't you supposed to assign the `loadNibNamed:owner:options:`'s return value to `self.view`? – Iulian Onofrei Mar 09 '17 at 20:46
  • @Garfbargle, Also, some guy says that [you should not add size constraints in the subclass](https://medium.com/@NSomar/auto-layout-best-practices-for-minimum-pain-c130b2b1a0f6#.3nzp6ruzz), and it makes sense, as the parent always lays out it's subviews. – Iulian Onofrei Mar 09 '17 at 20:50
57

Swift 3&4 Update to the accepted answer

1. Create a new UIView named 'DesignableXibView'

  • File > New > File > Source > Cocoa Touch Class > UIView

2. Create a matching xib file named 'DesignableXibView'

  • File > New > File > User Interface > View

3. Set the file owner of the of the xib

Select the "DesignableXibView.xib" > "File's Owner" > set "Custom Class" to 'DesignableXibView' in the Identity Inspector.

  • Note: Do not set the custom class of the view on the xib. Only the File Owner!

4. DesignableXibView's Implementation

    import UIKit

@IBDesignable

class DesignableXibView: UIView {

    var contentView : UIView!

    override init(frame: CGRect) {
        super.init(frame: frame)
        xibSetup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        xibSetup()
    }

    func xibSetup() {
        contentView = loadViewFromNib()

        // use bounds not frame or it'll be offset
        contentView.frame = bounds

        // Make the view stretch with containing view
        contentView.autoresizingMask = [UIViewAutoresizing.flexibleWidth, UIViewAutoresizing.flexibleHeight]

        // Adding custom subview on top of our view 
        addSubview(contentView)
    }

    func loadViewFromNib() -> UIView! {

        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
        let view = nib.instantiate(withOwner: self, options: nil).first as! UIView

        return view
    }
} 

5 Test your reusable view in a storyboard

  • Open your storyboard

  • Add a view

  • Set that view's Custom Class

harsh_v
  • 3,193
  • 3
  • 34
  • 53
  • 2
    I can not get it to work in Xcode8/Swift3. Works fine when running the app but does not show in storyboard. – shallowThought Oct 20 '16 at 17:58
  • Works fine for me. If you are trying to view changes on the xib it won't show. Add a view inside other controller and then set the class of that view to `DesignableXibView`. Build the project to view the changes. – harsh_v Oct 20 '16 at 18:53
  • 1
    Worked for me too. Just needed to add the DesignableXibView to my current storyboard as described in Step 5 of Garfbargle post – Tinkerbell Nov 29 '16 at 08:06
  • Just me getting "Could not load NIB in bundle" at run time? – Jonny Apr 11 '17 at 10:23
  • I'm guessing you cannot use String(describing: self) safely. It defaults to a long debug description atm. That code was not future safe..? – Jonny Apr 11 '17 at 10:26
  • 1
    @Jonny I think `String(describing: type(of: self))` should do this safely – harsh_v Apr 11 '17 at 12:02
  • In XCode 9.2 in order to get the xib to show in Storyboard I had to create the contentView as an outlet not just a variable. The provided examples do not show this. Otherwise, I would only see my view when I ran the app. –  Dec 07 '17 at 02:55
  • I've not checked it on XCode 9.2 as there are reports of bugs in that version, particularly while uploading binary on App Store. And to me, this looks like a bug. – harsh_v Dec 07 '17 at 04:37
11

The initWithCoder function in swift 2 if anybody is having trouble translating it:

required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        UINib(nibName: String(self.dynamicType), bundle: NSBundle.mainBundle()).instantiateWithOwner(self, options: nil)
        self.addSubview(view)
        self.view.translatesAutoresizingMaskIntoConstraints = false
        self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[view]|", options: NSLayoutFormatOptions.AlignAllCenterY , metrics: nil, views: ["view": self.view]))
        self.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[view]|", options: NSLayoutFormatOptions.AlignAllCenterX , metrics: nil, views: ["view": self.view]))
    }
SimplGy
  • 20,079
  • 15
  • 107
  • 144
Ankit Goel
  • 6,257
  • 4
  • 36
  • 48
3

For anyone trying to adapt accepted answer (by @Garfbargle) to Objective-C

Just converting Swift to Objective-C isn't enough to make it work. I've had hard time to allow live rendering in Storyboard.

After translating the whole code, the view is well loaded when running on device (or simulator), but the live rendering in Storyboard doesn't work. The reason for this is that I used [NSBundle mainBundle] whereas Interface Builder hasn't got access to mainBundle. What you have to use instead is [NSBundle bundleForClass:self.classForCoder]. BOOM, live rendering works now !

Note : if you have having issues with Auto Layout, try disabling Safe Area Layout Guides in the Xib.

For your convenience, I leave my whole code here, so that you just have to copy/paste (for all process, follow original answer) :

BottomBarView.h

#import <UIKit/UIKit.h>

IB_DESIGNABLE
@interface BottomBarView : UIView

@end

BottomBarView.m

#import "BottomBarView.h"

@interface BottomBarView() {
    UIView *contentView;
}

@end


@implementation BottomBarView

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

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

-(void) xibSetup {
    contentView = [self loadViewFromNib];

    contentView.frame = self.bounds;

    contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

    [self addSubview:contentView];
}

-(UIView*) loadViewFromNib {
    NSBundle *bundle = [NSBundle bundleForClass:self.classForCoder]; //this is the important line for view to render in IB
    UINib *nib = [UINib nibWithNibName:NSStringFromClass([self class]) bundle:bundle];
    UIView *view = [nib instantiateWithOwner:self options:nil][0];

    return view;
}

@end

Tell me if you encounter some issues but it should almost work out of the box :)

AnthoPak
  • 4,191
  • 3
  • 23
  • 41
1

Here's the answer you've wanted all along. You can just create your CustomView class, have the master instance of it in a xib with all the subviews and outlets. Then you can apply that class to any instances in your storyboards or other xibs.

No need to fiddle with File's Owner, or connect outlets to a proxy or modify the xib in a peculiar way, or add an instance of your custom view as a subview of itself.

Just do this:

  1. Import BFWControls framework
  2. Change your superclass from UIView to NibView (or from UITableViewCell to NibTableViewCell)

That's it!

It even works with IBDesignable to render your custom view (including the subviews from the xib) at design time in the storyboard.

You can read more about it here: https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155

And you can get the open source BFWControls framework here: https://github.com/BareFeetWare/BFWControls

Tom

barefeettom
  • 121
  • 1
  • 5
  • Man this is one of the best frameworks I have installed in years. Thank you so much for putting your effort on it. It works perfectly with Xcode 12.5, swift 5, iOS 14, etc. – Joaquin Pereira Aug 01 '21 at 06:18
0

If someone is interested, here's the Xamarin.iOS version of the code Step 4 of @Garfbargle

public partial class CustomView : UIView
    {
        public ErrorView(IntPtr handle) : base(handle)
        {

        }

        [Export("awakeFromNib")]
        public override void AwakeFromNib()
        {
            var nibObjects = NSBundle.MainBundle.LoadNib("CustomView", this, null);
            var view = (UIView)Runtime.GetNSObject(nibObjects.ValueAt(0));
            view.Frame = Bounds;
            view.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;

            AddSubview(rootView);
        }
    }
Yohan Dahmani
  • 1,670
  • 21
  • 33