Download view from external xib file in storyboard

I want to use a view in a variety of viewcontrollers in a storyboard. So I was thinking of designing the view in external xib, so the changes are reflected in each view controller. But how can I load a view from an external xib into a storyboard, and is it possible? If this is not the case, what other alternatives are available to meet the situation?

+113
ios uiview xib storyboard
Feb 14 '12 at 18:43
source share
11 answers

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

layout

Add .swift and .xib files to your project, each with the same name. The .xib file contains your custom view layout (preferably using automatic layout restrictions).

Make the swift file the owner of the xib file.

enter image description here The code

Add the following code to the .swift file and connect the outputs 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 own look anywhere in your storyboard. Just add a UIView and set the class name for your custom class name.

enter image description here

+111
Dec 30 '15 at 7:02
source share

For some time , Christopher Swazi 's approach was the best approach I found. I asked about this a couple of senior developers in my team, and one of them had the perfect solution ! It satisfies all the problems that Christopher Swazy so eloquently considered, and does not require the template code of the subclass (my main task is his approach). There is one mistake , but it is quite intuitive and easy to implement.

  1. Create a custom UIView class in a .swift file to manage your XIB. those. MyCustomClass.swift
  2. Create a .xib file and style it as you wish. those. MyCustomClass.xib
  3. Set your File Owner .xib to your custom class ( MyCustomClass )
  4. GOTCHA: Leave the class value (under identity Inspector ) for your user view in the .xib file empty. Thus, your custom view will not have a specific class, but it will have the specified File Owner.
  5. Plug in your sockets as usual using the Assistant Editor .
    • NOTE. If you look at the Connections Inspector you will notice that your reference outputs do not reference your custom class (for example, MyCustomClass ), but rather refer to File Owner . Since File Owner is listed as your custom class, sockets will be plugged in and will work correctly.
  6. Make sure your custom class has @IBDesignable before the class statement.
  7. Make your own class conform to the NibLoadable protocol below.
    • NOTE. If the name of the custom .swift class .swift is different from the .xib of the .xib file, set the nibName property nibName name of your .xib file.
  8. required init?(coder aDecoder: NSCoder) and override init(frame: CGRect) to call setupFromNib() as in the example below.
  9. Add a UIView to the desired storyboard and set your own class name (for example, MyCustomClass ) as a class.
  10. Watch IBDesignable in action as it draws your .xib in a storyboard with all this thrill and wonder.

Here is the protocol you 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 (file MyCustomClass.xib 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 skip Gotcha and set the class value in your .xib file as your own class, it will not be EXC_BAD_ACCESS in the storyboard, and you will get the EXC_BAD_ACCESS error EXC_BAD_ACCESS you start the application, because it gets stuck at infinity. Loop attempt to initialize the class from the pen using the init?(coder aDecoder: NSCoder) method init?(coder aDecoder: NSCoder) which then calls Self.nib.instantiate and calls init again.

+58
Nov 14 '17 at 21:59
source share

Assuming you created the xib 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 an xib-based view as a subclause of this view during 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) If you want to use it in your storyboard, add a UIView, as usual, select the newly added view, go to the Identity Inspector (the third icon in the upper right corner, which looks like a rectangle with lines in it) and enter the name of the subclass as " class "in the" Custom class "section.

+30
Jan 19 '16 at 15:47
source share

I always found the “add it as a subview” solution unsatisfactory, seeing that it was screwed with (1) autolayout, (2) @IBInspectable and (3) outputs. Instead, let me introduce you to the magic of awakeAfter: in the NSObject method.

awakeAfter allows you to completely replace an object that actually woke up from the NIB / Storyboard with another object. This object then goes through the hydration process, awakeFromNib is awakeFromNib , added as a view, etc.

We can use this in the "cutout" subclass of our view, whose sole purpose is to load the view from the NIB and return it for use in the storyboard. The embeddable subclass is then specified in the identity inspector of the Storyboard view, and not in the source class. Actually, it does not have to be a subclass for this to work, but by making it a subclass, IB can see any IBInspectable / IBOutlet properties.

This additional template may not seem optimal - and in a sense it is because, ideally, a UIStoryboard would be unhindered with this - but it has the advantage that the original subclass of NIB and UIView is not completely UIView . The role that he plays is mainly the adapter or bridge class, and it is perfectly acceptable from the point of view of design as an additional class, even if it is regrettable. On the other hand, if you prefer to save on your classes, the @BenPatch solution works by implementing the protocol with some other minor changes. The question of which solution is better comes down to the programmer's style: do you prefer composition of objects or multiple inheritance.

Note: the class set for presentation in the NIB file remains the same. An embedded subclass is used only in storyboards. A subclass cannot be used to instantiate a view in code, so it should not have any additional logic. It should contain an awakeAfter hook awakeAfter .

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

Significant The only significant drawback here is that if you define restrictions on the width, height, or aspect ratio of the storyboard that are not related to another view, you must manually copy them. The constraints that link the two views are set on the closest common ancestor, and the views are waking up from the storyboard from the inside, so by the time these restrictions are hydrated in the superview, the exchange has already occurred. Constraints that include only the view in question are set directly to this view and are thus discarded when an exchange occurs, if they are not copied.

Please note that there is a restriction set for the view in the storyboard, which is copied to the newly created view, which may already have its own restrictions defined in its pen file. Those are not affected.

 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 UIView extension of UIView . All he does is iterate over the NIB objects until he finds one that matches the type. Note that the generic type is a return value, so the type must be specified on 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 } } 
+26
Oct 31 '16 at 13:25
source share

The best solution for now is to just use a custom view controller with its view defined in xib, and just remove the “view” property that Xcode creates inside the storyboard when adding a view controller to it (remember to set the name to a custom class, though).

This will cause the runtime to automatically search for the XIB and load it. You can use this trick for any kind of container views or content views.

+5
Mar 10 '15 at 23:20
source share

I am thinking of an alternative using XIB views to use the View Controller in a separate storyboard .

Then, in the main storyboard, instead of a custom view, use a container view with an Embed Segue and set the StoryboardReference to this custom view controller, whose view should be placed inside another view in the main storyboard.

Then we can set up delegation and communication between this embedded ViewController and the main view controller in preparation for the transition . This approach is different from displaying a UIView, but much simpler and more efficient (from a programming point of view) can be used to achieve the same goal, that is, have a reusable custom view that is visible in the main storyboard

An additional advantage is that you can implement your logic in the CustomViewController class and configure the entire delegation and presentation preparation without creating separate (harder to find in the project) controller classes and without placing standard code in the main UIViewController using Component. I think this is good for reusable components e.g. A component of a music player (like a widget) that integrates into other views.

+5
Mar 06 '18 at 20:26
source share

You can use this solution even if your class does not have the same name as XIB. For example, if you have a BaseA class controller controller that has the XIB name of A.xib controller and you subclass it with controller B and want to create an instance of controller B in the storyboard, you can:

  • create a storyboard view controller
  • set the controller class to the controller.
  • delete controller B view in storyboard
  • override the type of boot in controller A:

*

 - (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]; } } 
0
Apr 08 '15 at 6:55
source share

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

Use the 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 the files MyView.h , MyView.m and MyView.xib .

First prepare MyView.xib as Ben Patch's answer says , so set the MyView class for the file owner instead of the 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 programmatically:

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

Warning! A preview of this view will not appear on the storyboard if you use the WatchKit extension due to this error in Xcode> = 9.2: https://forums.developer.apple.com/thread/95616

0
Aug 02 '18 at 11:43
source share

Here is the answer you wanted all the time. You can simply create your own CustomView class, have its main instance in xib with all subviews and exits. You can then apply this class to any instances in your storyboards or other Xibs.

There is no need to bother with the owner of the file, connect the outputs to the proxy server, modify the XIB in a special way, or add an instance of your user view as your own subview.

Just do it:

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

It!

It even works with IBDesignable to reference your custom view (including subviews from xib) during development in a 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 environment here: https://github.com/BareFeetWare/BFWControls

And here is a simple excerpt from the NibReplaceable code that excites it, if you're interested: https://gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4

Tom 👣

0
Mar 09 '19 at 12:39 on
source share

If someone is struggling to solve this problem in quick 4/5, follow this guide! No other solution helped me.

https://medium.com/better-programming/swift-3-creating-a-custom-view-from-a-xib-ecdfe5b3a960 (updated for Swift 5)

0
Jul 24 '19 at 13:44 on
source share

Although the most popular answers work fine, they are wrong. They all use File owner as a connection between class outputs and user interface components, and this is very wrong. File owner supposed to be used only for top-level objects, not UIView . Check out the Apple Developer Doc . Having UIView as a File owner leads to these undesirable consequences.

  1. You are forced to use contentView when you should use only self . It is ugly.
  2. You can only have one UIView on Xib. Xib is supposed to have several UIViews.

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

0
Aug 02 '19 at 21:05
source share



All Articles