142

I want to use a view throughout multiple viewcontrollers in a storyboard. Thus, I thought about designing the view in an external xib so changes are reflected in every viewcontroller. But how can one load a view from a external xib in a storyboard and is it even possible? If thats not the case, what other alternatives are availble to suit the situation abouve?

Lorenzo B
  • 33,006
  • 23
  • 110
  • 185
Sebastian Hoffmann
  • 10,024
  • 5
  • 43
  • 74
  • 2
    possible duplicate of [How do I create a custom iOS view class and instantiate multiple copies of it (in IB)?](http://stackoverflow.com/questions/9251202/how-do-i-create-a-custom-ios-view-class-and-instantiate-multiple-copies-of-it-i) – Abizern Apr 12 '13 at 08:50
  • 1
    See this video: http://www.youtube.com/watch?v=o3MawJVxTgk – malhobayyeb Nov 03 '14 at 05:47

10 Answers10

148

My full example is here, but I will provide a summary below.

Layout

Add a .swift and .xib file each with the same name to your project. The .xib file contains your custom view layout (using auto layout constraints preferably).

Make the swift file the xib file's owner.

enter image description here Code

Add the following code to the .swift file and hook up the outlets and actions from the .xib file.

import UIKit
class ResuableCustomView: UIView {

    let nibName = "ReusableCustomView"
    var contentView: UIView?

    @IBOutlet weak var label: UILabel!
    @IBAction func buttonTap(_ sender: UIButton) {
        label.text = "Hi"
    }

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

        guard let view = loadViewFromNib() else { return }
        view.frame = self.bounds
        self.addSubview(view)
        contentView = view
    }

    func loadViewFromNib() -> UIView? {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: nibName, bundle: bundle)
        return nib.instantiate(withOwner: self, options: nil).first as? UIView
    }
}

Use it

Use your custom view anywhere in your storyboard. Just add a UIView and set the class name to your custom class name.

enter image description here

Suragch
  • 364,799
  • 232
  • 1,155
  • 1,198
  • 5
    Isn't it that loadNibNamed calls init(coder:)? I have a crash trying to adapt your approach. – Fishman Nov 14 '16 at 09:15
  • 1
    @Fishman, if you try to load the view programmatically (rather than from the storyboard), it will crash because it doesn't currently have an `init(frame:)`. See [this tutorial](http://supereasyapps.com/blog/2014/12/15/create-an-ibdesignable-uiview-subclass-with-code-from-an-xib-file-in-xcode-6) for more details. – Suragch Jan 06 '17 at 08:18
  • 8
    Another common cause of crashing is not setting the custom view to the *file's owner*. See the red circle in my answer. – Suragch Jan 06 '17 at 08:22
  • 8
    Yeah I had set the class of the root view instead of the file owner and it was causing an infinite loop. – devios1 Feb 24 '17 at 00:31
  • @Suragch ResuableCustomView not resizing. displaying the same in all devices. Not changing height & width according to super of custom view. – Pramod Tapaniya Aug 28 '18 at 08:56
  • @PramodTapaniya, my guess would be that you didn't set the autolayout constraints. – Suragch Aug 28 '18 at 10:49
  • 2
    After adding view.autoresizingMask = [.flexibleWidth, .flexibleHeight] line before self.addSubview(view) it's working perfectly. – Pramod Tapaniya Aug 28 '18 at 12:25
  • I try your tutorial, But its creating 2 view at the time. like one above one – Kathiresan Murugan Apr 03 '19 at 05:35
  • @Kathiresan.M, The custom view that I created as an example here is two views (a label and a button). You can modify the layout to be whatever you want your custom view to look like. – Suragch Apr 04 '19 at 06:55
  • No iam not telling that. I am telling ResuableCustomView is created one above one. – Kathiresan Murugan Apr 04 '19 at 07:00
  • @Kathiresan.M, Oh, I see. In that case I don't know what the problem is. Sorry. If you figure it out, leave another comment or answer. – Suragch Apr 05 '19 at 06:21
  • https://www.youtube.com/watch?v=H-55qZYc9qI its very simple just follow the video. – Mehul Chuahan Jul 03 '19 at 11:51
  • @Suragch self.addSubview(view) makes no sense for me. As I see it - self must become the same view initialised from xib. ResuableCustomView is not the xib view, instead it contains it. Is it a workaround, and it won't work the other way? – Luten Apr 22 '20 at 06:59
88

For a while Christopher Swasey's approach was the best approach I had found. I asked a couple of the senior devs on my team about it and one of them had the perfect solution! It satisfies every one of the concerns that Christopher Swasey so eloquently addressed and it doesn't require boilerplate subclass code(my main concern with his approach). There is one gotcha, but other than that it is fairly intuitive and easy to implement.

  1. Create a custom UIView class in a .swift file to control your xib. i.e. MyCustomClass.swift
  2. Create a .xib file and style it as you want. i.e. MyCustomClass.xib
  3. Set the File's Owner of the .xib file to be your custom class (MyCustomClass)
  4. GOTCHA: leave the class value (under the identity Inspector) for your custom view in the .xib file blank. So your custom view will have no specified class, but it will have a specified File's Owner.
  5. Hook up your outlets as you normally would using the Assistant Editor.
    • NOTE: If you look at the Connections Inspector you will notice that your Referencing Outlets do not reference your custom class (i.e. MyCustomClass), but rather reference File's Owner. Since File's Owner is specified to be your custom class, the outlets will hook up and work propery.
  6. Make sure your custom class has @IBDesignable before the class statement.
  7. Make your custom class conform to the NibLoadable protocol referenced below.
    • NOTE: If your custom class .swift file name is different from your .xib file name, then set the nibName property to be the name of your .xib file.
  8. Implement required init?(coder aDecoder: NSCoder) and override init(frame: CGRect) to call setupFromNib() like the example below.
  9. Add a UIView to your desired storyboard and set the class to be your custom class name (i.e. MyCustomClass).
  10. Watch IBDesignable in action as it draws your .xib in the storyboard with all of it's awe and wonder.

Here is the protocol you will want to reference:

public protocol NibLoadable {
    static var nibName: String { get }
}

public extension NibLoadable where Self: UIView {

    public static var nibName: String {
        return String(describing: Self.self) // defaults to the name of the class implementing this protocol.
    }

    public static var nib: UINib {
        let bundle = Bundle(for: Self.self)
        return UINib(nibName: Self.nibName, bundle: bundle)
    }

    func setupFromNib() {
        guard let view = Self.nib.instantiate(withOwner: self, options: nil).first as? UIView else { fatalError("Error loading \(self) from nib") }
        addSubview(view)
        view.translatesAutoresizingMaskIntoConstraints = false
        view.leadingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.leadingAnchor, constant: 0).isActive = true
        view.topAnchor.constraint(equalTo: self.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
        view.trailingAnchor.constraint(equalTo: self.safeAreaLayoutGuide.trailingAnchor, constant: 0).isActive = true
        view.bottomAnchor.constraint(equalTo: self.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
    }
}

And here is an example of MyCustomClass that implements the protocol (with the .xib file being named MyCustomClass.xib):

@IBDesignable
class MyCustomClass: UIView, NibLoadable {

    @IBOutlet weak var myLabel: UILabel!

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

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

}

NOTE: If you miss the Gotcha and set the class value inside your .xib file to be your custom class, then it will not draw in the storyboard and you will get a EXC_BAD_ACCESS error when you run the app because it gets stuck in an infinite loop of trying to initialize the class from the nib using the init?(coder aDecoder: NSCoder) method which then calls Self.nib.instantiate and calls the init again.

Ben Patch
  • 1,057
  • 8
  • 11
  • 2
    Here is another great approach to it, but I feel the above one is still better: https://medium.com/zenchef-tech-and-product/how-to-visualize-reusable-xibs-in-storyboards-using-ibdesignable-c0488c7f525d – Ben Patch Nov 14 '17 at 22:02
  • 4
    The approach mentioned by you above works perfectly and applies live previewing right in the storyboard. It's absolutely handy and awesome, big up! – Igor Leonovich Dec 21 '17 at 20:30
  • Perfect solution! Thanks a lot! I think it's the best solution for adding custom views in storyboards – Ivan Smetanin Jan 30 '18 at 12:02
  • Not to knock this solution—it's an interesting approach, and the more the merrier, as far as I'm concerned—but for the record I do not agree that this is in any way more straightforward than my answer. – Christopher Swasey Feb 27 '18 at 18:20
  • That's fair. Straightforward was the wrong word to use. I'll update the post. – Ben Patch Feb 28 '18 at 19:56
  • 1
    FYI: this solution, using constraint definition in `setupFromNib()`, seems to fix certain strange auto-layout issues with auto-sizing table view cells containing XIB -created views. – Gary Mar 07 '18 at 17:03
  • Great solution. Could you explain a little bit more? – thelearner May 15 '18 at 09:17
  • 1
    By far the best solution! I love `@IBDesignable` compatibility. Can't believe why Xcode or UIKit doesn't provide something like this as default when adding a UIView file. – heyfrank Jun 29 '18 at 12:20
  • 1
    can't seem to make this one work.. Every time I set File Owner inside my .xib file, it ALSO sets custom class. – drewster Jul 13 '18 at 19:03
  • Great answer! Actually you don't need to use a protocol, just move `nibName`, `nib` and `setupFromNib()` into your class and make it return your class name with `Bundle(for: type(of: self))` – Dorian Roy Aug 28 '18 at 16:24
  • @BenPatch have you been able to avoid boilerplate in all the views with this approach? e.g. see here: https://stackoverflow.com/questions/55464958/get-the-type-name-of-derived-type-in-a-static-method-of-base-class-or-extension – zaitsman Apr 01 '19 at 23:56
  • I still didn't understand how to link these at storyboard IB – JBarros35 Sep 26 '19 at 09:56
31

Assuming that you've created an xib that you want to use:

1) Create a custom subclass of UIView (you can go to File -> New -> File... -> Cocoa Touch Class. Make sure "Subclass of:" is "UIView").

2) Add a view that's based on the xib as a subview to this view at initialization.

In Obj-C

-(id)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super initWithCoder:aDecoder]) {
        UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:@"YourXIBFilename"
                                                              owner:self
                                                            options:nil] objectAtIndex:0];
        xibView.frame = self.bounds;
        xibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
        [self addSubview: xibView];
    }
    return self;
}

In Swift 2

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = NSBundle.mainBundle().loadNibNamed("YourXIBFilename", owner: self, options: nil)[0] as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
    self.addSubview(xibView)
}

In Swift 3

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    let xibView = Bundle.main.loadNibNamed("YourXIBFilename", owner: self, options: nil)!.first as! UIView
    xibView.frame = self.bounds
    xibView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
    self.addSubview(xibView)
}

3) Wherever you want to use it in your storyboard, add a UIView as you normally would, select the newly added view, go to the Identity Inspector (the third icon on the upper right that looks like a rectangle with lines in it), and enter your subclass's name in as the "Class" under "Custom Class".

user1021430
  • 2,740
  • 3
  • 26
  • 32
  • `xibView.frame = self.frame;` should be `xibView.frame = CGRectMake(0, 0, self.frame.size.width, self.frame.size.height);`, otherwise xibView will have an offset when the view is added to storyboard. – BabyPanda Mar 01 '16 at 13:09
  • late to the party but it seems he changed it to xibView.frame = self.bounds, which is a frame without offset – Heavy_Bullets Dec 21 '16 at 13:07
  • 23
    Results in a crash due to infinite recursion. Loading the nib creates another instance of the subclass. – David Mar 05 '17 at 20:59
  • 2
    The xib view's class should not be the same as this new subclass. If the xib is MyClass, you can make this new class MyClassContainer. – user1021430 May 26 '17 at 17:57
  • 1
    the solution above will crash to infinite recursion. – JBarros35 Jun 29 '20 at 17:37
30

I've always found the "add it as a subview" solution unsatisfactory, seeing as it screws with (1) autolayout, (2) @IBInspectable, and (3) outlets. Instead, let me introduce you to the magic of awakeAfter:, an NSObject method.

awakeAfter lets you swap out the object actually woken up from a NIB/Storyboard with a different object entirely. That object is then put through the hydration process, has awakeFromNib called on it, is added as a view, etc.

We can use this in a "cardboard cut-out" subclass of our view, the only purpose of which will be to load the view from the NIB and return it for use in the Storyboard. The embeddable subclass is then specified in the Storyboard view's identity inspector, rather than the original class. It doesn't actually have to be a subclass in order for this to work, but making it a subclass is what allows IB to see any IBInspectable/IBOutlet properties.

This extra boilerplate might seem suboptimal—and in a sense it is, because ideally UIStoryboard would handle this seamlessly—but it has the advantage of leaving the original NIB and UIView subclass completely unmodified. The role it plays is basically that of an adapter or bridge class, and is perfectly valid, design-wise, as an additional class, even if it is regrettable. On the flip side, if you prefer to be parsimonious with your classes, @BenPatch's solution works by implementing a protocol with some other minor changes. The question of which solution is better boils down to a matter of programmer style: whether one prefers object composition or multiple inheritance.

Note: the class set on the view in the NIB file remains the same. The embeddable subclass is only used in the storyboard. The subclass can't be used to instantiate the view in code, so it shouldn't have any additional logic, itself. It should only contain the awakeAfter hook.

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any
  }
}

⚠️ The one significant drawback here is that if you define width, height, or aspect ratio constraints in the storyboard that don't relate to another view then they have to be copied over manually. Constraints that relate two views are installed on the nearest common ancestor, and views are woken from the storyboard from the inside-out, so by the time those constraints are hydrated on the superview the swap has already occurred. Constraints that only involve the view in question are installed directly on that view, and thus get tossed when the swap occurs unless they are copied.

Note that what is happening here is constraints installed on the view in the storyboard are copied to the newly instantiated view, which may already have constraints of its own, defined in its nib file. Those are unaffected.

class MyCustomEmbeddableView: MyCustomView {
  override func awakeAfter(using aDecoder: NSCoder) -> Any? {
    let newView = (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)!

    for constraint in constraints {
      if constraint.secondItem != nil {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: newView, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant))
      } else {
        newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: constraint.constant))
      }
    }

    return newView as Any
  }
}  

instantiateViewFromNib is a type-safe extension to UIView. All it does is loop through the NIB's objects until it finds one that matches the type. Note that the generic type is the return value, so the type has to be specified at the call site.

extension UIView {
  public class func instantiateViewFromNib<T>(_ nibName: String, inBundle bundle: Bundle = Bundle.main) -> T? {
    if let objects = bundle.loadNibNamed(nibName, owner: nil) {
      for object in objects {
        if let object = object as? T {
          return object
        }
      }
    }

    return nil
  }
}
Christopher Swasey
  • 9,842
  • 1
  • 29
  • 23
  • That is mindboggling. If I'm not mistaken though, this only works "on the storyboard" - if you try to create such a class in code at runtime, I don't think it works. I believe. – Fattie Jan 14 '17 at 14:30
  • The subclass should work in code just as well as the original class for all intents and purposes. If you want to load the view from a nib in code you would just instantiate it directly using the same technique. All the subclass does is take the code to instantiate the view from a nib and put it in a hook for the storyboard to use. – Christopher Swasey Jan 16 '17 at 17:31
  • Actually I was wrong—it *would* work just as well if you could instantiate it, but you can't, because the view in the NIB will have the superclass as its type so `instantiateViewFromNib` won't return anything. Not a big deal either way IMO, the subclass is just a contrivance to hook into the storyboard, all the code should be on the original class. – Christopher Swasey Jan 18 '17 at 19:07
  • 1
    Great! One thing tripped me up because I have little experience with xibs (I only ever worked with storyboards & programmatic approaches), leaving this here in case it helps someone: in the .xib file, you need to select the top level view, and set its class type to `MyCustomView`. In my xib the left inner-sidebar was missing by default; to turn it on, there's a button next to the "View as: iPhone 7" traits control near the bottom/left side. – xaphod Mar 23 '17 at 23:50
  • 5
    It brakes constraints when it replaced by another object. :( – invoodoo May 05 '17 at 22:42
  • It's possible you're using constraints in a way that breaks. This method does work transparently with constraints defined in IB. – Christopher Swasey Oct 17 '17 at 15:51
  • @ChristopherSwasey One behavior I'm seeing is that constraints added directly to `MyCustomEmbeddableView` in the storyboard (i.e., a fixed height constraint) don't appear to get transferred to the new view. Constraints added from `MyCustomEmbeddableView` to the superview do. In `awakeAfter` of `MyCustomEmbeddableView`, `self.constraints` only has the height constraint and no constraints to the superview, while the `view.constraints` of the new view has all the superview constraints and no height constraint. Have you seen this before? – Evan R Mar 06 '18 at 12:42
  • @Evan R Looking into it... would be super frustrating if that is the case, and that would explain other mentions of it not working with autolayout. I'm hoping there's a relatively straightforward solution. – Christopher Swasey Mar 06 '18 at 18:01
  • Somewhat improbably, I guess I must never have used this technique with a simple height/width constraint. Other constraints that relate to parent or sibling views are installed on higher views in the hierarchy, so by the time they get hooked up the awakeAfter has swapped out the views. Constraints that are installed on the view itself, however, are instantiated as part of the original view decoding. This might be the death knell for this solution. I might could come up with a workaround, but probably at the cost of the simplicity. Will keep noodling on it. – Christopher Swasey Mar 06 '18 at 18:10
  • @ChristopherSwasey Yes, this is the behavior I was seeing. A few potential workarounds: if possible, make the width and height constraints as a relation to the superview in the storyboard (i.e., create an equal height constraint to the superview and modify the multiplier) instead of as fixed constraints, or in `awakeAfter` transfer over any fixed height/width constraints from `MyCustomEmbeddableView` to the new instance of `MyCustomView`. You'd just have to recreate those constraints anew save for the constants, as they go to different view hierarchies. I still think your solution is the best. – Evan R Mar 06 '18 at 22:28
  • I've updated the answer to note the problem with width/height constraints and to add a copy-and-paste solution. It's not as elegant anymore, but it could be a lot worse. – Christopher Swasey Mar 07 '18 at 17:36
  • @ChristopherSwasey I've found what seems to be another issue with this approach—I'm seeing very frequent crashes with using size class-based constraints on the view (i.e., height = x for regular width devices only, constraint not installed for all cases). The exception thrown is very vague and took a while to track down. – Evan R Apr 11 '18 at 12:29
  • @EvanR Damn. The edge cases are piling up. That one might be intractable, if references are somehow being kept to the height/width constraint objects that are being copied and dumped. Will noodle on this but not hopeful. If only storyboards respected awakeAfter: before it did its thing... although I guess then it wouldn't be `awakeAfter` – Christopher Swasey Apr 19 '18 at 12:27
9

Although the top most popular answers works fine, they are conceptually wrong. They all use File's owner as connection between class's outlets and UI components. File's owner is supposed to be used only for top-level objects not UIViews. Check out Apple developer document. Having UIView as File's owner leads to these undesirable consequences.

  1. You are forced to use contentView where you are supposed to use self. It’s not only ugly, but also structurally wrong because the intermediate view keeps data structure from conveying it’s UI structure. It's like going against the concept of declarative UI.
  2. You can only have one UIView per Xib. An Xib is supposed to have multiple UIViews.

There's elegant way to do it without using File's owner. Please check this blog post. It explains how to do it the right way.

Ingun전인건
  • 406
  • 3
  • 7
  • it doesn't not matter at all you didn't explained why its bad. I can't see why. there are no side effects. – JBarros35 Jun 29 '20 at 17:40
7

I think about alternative for using XIB views to be using View Controller in separate storyboard.

Then in main storyboard in place of custom view use container view with Embed Segue and have StoryboardReference to this custom view controller which view should be placed inside other view in main storyboard.

Then we can set up delegation and communication between this embed ViewController and main view controller through prepare for segue. This approach is different then displaying UIView, but much simpler and more efficiently (from programming perspective) can be utilised to achieve the same goal, i.e. have reusable custom view that is visible in main storyboard

The additional advantage is that you can implement you logic in CustomViewController class and there set up all delegation and view preparation without creating separate (harder to find in project) controller classes, and without placing boilerplate code in main UIViewController using Component. I think this is good for reusable components ex. Music Player component (widget like) that is embeddable in other views.

Michał Ziobro
  • 7,390
  • 6
  • 50
  • 99
  • Indeed sadly much more simple solution than using custom view xib :( – Wilson Apr 11 '18 at 12:27
  • 1
    after going in circles with apple's xib lifecycle creation chain on custom UIView this is the way I ended up going. – LightningStryk Oct 25 '18 at 22:57
  • In case if you want to add the custom view dynamically, we still have to go with separate view controller (most preferably XIB) – Satyam Jun 13 '20 at 13:42
6

Best solution currently is to just use a custom view controller with its view defined in a xib, and simply delete the "view" property that Xcode creates inside the storyboard when adding the view controller to it (don't forget to set the name of the custom class though).

This will make the runtime automatically look for the xib and load it. You can use this trick for any kind of container views, or content view.

shim
  • 7,170
  • 10
  • 62
  • 95
Ben G
  • 3,865
  • 3
  • 28
  • 31
1

Solution for Objective-C according to steps described in Ben Patch's response.

Use extension for UIView:

@implementation UIView (NibLoadable)

- (UIView*)loadFromNib
{
    UIView *xibView = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil] firstObject];
    xibView.translatesAutoresizingMaskIntoConstraints = NO;
    [self addSubview:xibView];
    [xibView.topAnchor constraintEqualToAnchor:self.topAnchor].active = YES;
    [xibView.bottomAnchor constraintEqualToAnchor:self.bottomAnchor].active = YES;
    [xibView.leftAnchor constraintEqualToAnchor:self.leftAnchor].active = YES;
    [xibView.rightAnchor constraintEqualToAnchor:self.rightAnchor].active = YES;
    return xibView;
}

@end

Create files MyView.h, MyView.m and MyView.xib.

First prepare your MyView.xib as Ben Patch's response says so set class MyView for File's owner instead of main view inside this XIB.

MyView.h:

#import <UIKit/UIKit.h>

IB_DESIGNABLE @interface MyView : UIView

@property (nonatomic, weak) IBOutlet UIView* someSubview;

@end

MyView.m:

#import "MyView.h"
#import "UIView+NibLoadable.h"

@implementation MyView

#pragma mark - Initializers

- (id)init
{
    self = [super init];
    if (self) {
        [self loadFromNib];
        [self internalInit];
    }
    return self;
}

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

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

- (void)awakeFromNib
{
    [super awakeFromNib];
    [self internalInit];
}

- (void)internalInit
{
    // Custom initialization.
}

@end

And later just create your view programatically:

MyView* view = [[MyView alloc] init];

Warning! Preview of this view will not be shown in Storyboard if you use WatchKit Extension because of this bug in Xcode >= 9.2: https://forums.developer.apple.com/thread/95616

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 refer 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

And here's a simple extract of the NibReplaceable code that drives it, in case you're curious:
https://gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4

Tom

  • I don't want to install a framework when it should be provided natively. – JBarros35 Jun 29 '20 at 17:39
  • @JBarros35: I also wish it was provided natively, but unfortunately it is not. That's why I created the framework. You can instead write your own code based on the small gist source code I posted above, or by picking at the source code of the open source BFWControls framework above. But, that's a lot more work. Good luck . – barefeettom Dec 05 '20 at 04:07
0

This solution can be used even if your class does not have the same name as the XIB. For example, if you have a base view controller class controllerA which has a XIB name controllerA.xib and you subclassed this with controllerB and want to create an instance of controllerB in a storyboard, then you can:

  • create the view controller in the storyboard
  • set the class of the controller to the controllerB
  • delete the view of the controllerB in the storyboard
  • override load view in controllerA to:

*

- (void) loadView    
{
        //according to the documentation, if a nibName was passed in initWithNibName or
        //this controller was created from a storyboard (and the controller has a view), then nibname will be set
        //else it will be nil
        if (self.nibName)
        {
            //a nib was specified, respect that
            [super loadView];
        }
        else
        {
            //if no nib name, first try a nib which would have the same name as the class
            //if that fails, force to load from the base class nib
            //this is convenient for including a subclass of this controller
            //in a storyboard
            NSString *className = NSStringFromClass([self class]);
            NSString *pathToNIB = [[NSBundle bundleForClass:[self class]] pathForResource: className ofType:@"nib"];
            UINib *nib ;
            if (pathToNIB)
            {
                nib = [UINib nibWithNibName: className bundle: [NSBundle bundleForClass:[self class]]];
            }
            else
            {
                //force to load from nib so that all subclass will have the correct xib
                //this is convenient for including a subclass
                //in a storyboard
                nib = [UINib nibWithNibName: @"baseControllerXIB" bundle:[NSBundle bundleForClass:[self class]]];
            }

            self.view = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
       }
}
otusweb
  • 1,095
  • 1
  • 12
  • 23