61

in objective c it can be done in init method by

-(id)init{
    self = [[[NSBundle mainBundle] loadNibNamed:@"ViewBtnWishList" owner:0 options:nil]     objectAtIndex:0];
return self;
}

but when i do this in swift

init(frame: CGRect) {
    self = NSBundle.mainBundle().loadNibNamed("ViewDetailMenu", owner: 0, options: nil)[0] as? UIView
}

cannot assign to self in a method error is shown. now my approach is to create a view, and add the view loaded from nib to it. anyone have a better idea?

Siu Chung Chan
  • 1,686
  • 1
  • 14
  • 31
  • 1
    using `UINib` would be much more efficient... anyway, the `self` cannot be assigned in _Swift_. you may want to create a class method which inits your class using a _xib_ file. – holex Jun 23 '14 at 16:05

9 Answers9

131

for Swift 4

extension UIView {
    class func loadFromNibNamed(nibNamed: String, bundle: Bundle? = nil) -> UIView? {
      return UINib(
          nibName: nibNamed,
          bundle: bundle
      ).instantiate(withOwner: nil, options: nil)[0] as? UIView
  }
}

for Swift 3

You could create an extension on UIView:

extension UIView {
    class func loadFromNibNamed(nibNamed: String, bundle: NSBundle? = nil) -> UIView? {
        return UINib(
            nibName: nibNamed,
            bundle: bundle
        ).instantiateWithOwner(nil, options: nil)[0] as? UIView
    }
}

Note: Using UINib is faster because it does caching for you.

Then you can just do:

ViewDetailItem.loadFromNibNamed("ViewBtnWishList")

And you will be able to reuse that method on any view.

drewag
  • 93,393
  • 28
  • 139
  • 128
  • why are you using the 3-4 times _slower_ `NSBundle` to load a nib? – holex Jun 23 '14 at 16:13
  • @holex I was just trying to keep that code consistent to address the actual question, but I updated it. – drewag Jun 23 '14 at 16:16
  • for me it is fine, I'm just asking. anyway, the `bundle` should be `nil` in that case if you are not using specific bundle but the default one. :) – holex Jun 23 '14 at 16:17
  • @holex, I believe there is an issue in the beta right now where using nil for the bundle does not work – drewag Jun 23 '14 at 16:18
  • I have not experienced such thing, I pass `nil` as bundle, and the nib is loaded properly; my code, I've posted as answer, is fully functioning. – holex Jun 23 '14 at 16:30
  • At this juncture, using NSStringFromClass may be problematic if you don't also use @objc to assign a specific Objective-C class name, since the mangled name will be returned otherwise. – David Berry Jun 23 '14 at 19:02
  • 2
    thanks,so i can create the custom uiview as follow?? var customView = ViewDetailItem.loadFromNibNamed("ViewBtnWishList") but what if I want to initialize some more variable when the custom view created? In this case, did we just bypass the init methods of UIView? – Siu Chung Chan Jun 24 '14 at 03:33
  • Don't forget the "import UIKit" line on top of the document – MB_iOSDeveloper Mar 19 '15 at 08:35
  • @drewag: Am I doing something wrong? [I'm trying to use](http://pastebin.com/2DCUztSk) and [my extension class](http://pastebin.com/gTakqrau) – el.severo Mar 26 '15 at 11:34
  • @el.severo and what problem are you having? – drewag Mar 26 '15 at 22:16
  • @drewag: I couldn't instantiate / load a view (.xib) file. It threw me a crash error. I ended up using different approach. I was trying to load some xib to iCarousel object – el.severo Mar 27 '15 at 00:38
  • @el.severo at brief inspection of the code I don't see a problem. Make sure the nib is named correctly and that it is actually being compiled into the binary – drewag Mar 27 '15 at 00:44
  • 3
    I have a problem if i use this in a layout which contains other View, and then i make outlet to the class, I'm getting `Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key infoView.'` – beretis Apr 30 '15 at 14:50
  • Is it possible to load the nib file inside the UIView itself? – Bagusflyer Jun 11 '15 at 04:13
  • 1
    Just curious - Why use the class func instead of static func – Pradeep Banavara Jul 17 '15 at 05:01
  • @beretis Check if you accidentally pin your infoView to File's Owner. You should rather pin it to the View. – Radek Wilczak Sep 11 '15 at 13:45
  • Really nice. Though I had more use of the function in a generic form: ``` class func loadFromNibNamed(nibNamed: String, bundle : NSBundle? = nil) -> T? { return UINib( nibName: nibNamed, bundle: bundle ).instantiateWithOwner(nil, options: nil)[0] as? T }``` – netdigger May 28 '16 at 16:21
  • Can you anyone tell me what is ViewDetailItem here? and In which file the extension method should be declared? – Praburaj Jul 25 '17 at 05:25
24

This worked for me.

override func awakeAfterUsingCoder(aDecoder: NSCoder) -> AnyObject? {
    if self.subviews.count == 0 {
        return loadNib()
    }
    return self
}

private func loadNib() -> YourCustomView {
    return NSBundle.mainBundle().loadNibNamed("YourCustomViewNibName", owner: nil, options: nil)[0] as YourCustomView
}
Dave
  • 4,038
  • 9
  • 45
  • 57
  • 2
    This is the best solution for reuse within a storyboard or xib file. If you simply add the loaded nib view as a subview, properties like delegates don't get set properly – Austin Sep 03 '15 at 14:04
  • Make sure if you have any outlets, that you connect them to the View in your XIB, not `File Owner`, or else you'll get KVO-compliance errors. – Dov Sep 29 '15 at 18:45
  • This is so far the only one that works for me. Thanks! – tyegah123 Nov 03 '15 at 09:24
  • 1
    The most appropriate solution in any way. I don't know why only 10 up votes here before I found it. – WeZZard Mar 17 '16 at 02:36
  • 1
    But unfortunately, when working with storyboard, this solution replace the object originally in the storyboard indeed, which leading a situation where your view constraints or any other setup done by storyboard gone disappear. – WeZZard Mar 17 '16 at 07:31
24

Tested in Xcode 7 beta 4 , Swift 2.0 . The following code will assign xib to the UIView. You can use this custom xib view in storyboard and access the IBOutlet object also.

import UIKit

@IBDesignable class SimpleCustomView:UIView
{
    var view:UIView!;

    @IBOutlet weak var lblTitle: UILabel!

   @IBInspectable var lblTitleText : String?
        {
        get{
            return lblTitle.text;
        }
        set(lblTitleText)
        {
            lblTitle.text = lblTitleText!;
        }
    }

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

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        loadViewFromNib ()
    }
    func loadViewFromNib() {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: "SimpleCustomView", bundle: bundle)
        let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
        view.frame = bounds
        view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        self.addSubview(view);



    }


}

Access customview programatically

self.customView =  SimpleCustomView(frame: CGRectMake(100, 100, 200, 200))
        self.view.addSubview(self.customView!);

Source code - https://github.com/karthikprabhuA/CustomXIBSwift

Dylan Hand
  • 1,190
  • 2
  • 14
  • 21
karthikPrabhu Alagu
  • 3,371
  • 1
  • 21
  • 25
  • 3
    Works in other XIBs as well - that's great. However - one problem, this isn't creating a custom view directly from a XIB. It is loading a UIView from a XIB and adding it as a subview to the custom view. – n13 Sep 24 '15 at 09:35
  • A most exellent contribution. Very clear and complete. THANKS for posting it with the Github repo too! One small issue the member variable "view" inside `SimpleCustomView.swift` appears to be unecessary and unused. – Biodave Oct 28 '15 at 16:37
  • one from a lot of solutions which isn't setup view from a static method) – hamsternik Aug 22 '17 at 15:15
18

that may be a solution for you:

Swift 3.x

class func instanceFromNib() -> UIView {
    return UINib(nibName: "<<NibFileName>>", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}

Swift 2.x

class func instanceFromNib() -> UIView {
    return UINib(nibName: "<<NibFileName>>", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as UIView
}
Community
  • 1
  • 1
holex
  • 23,961
  • 7
  • 62
  • 76
  • 1
    Perfect answer. Short, concise and works like a charm. Thanks! – kakubei Mar 24 '16 at 10:11
  • So would I put this code in the XIB's view controller or the view controller I'm trying to add the XIB to? – MarksCode May 16 '17 at 08:42
  • @MarksCode, you need to add this snippet to the subset of the `UIView` class; that is nothing to do with any view-conrollers in this context. – holex May 16 '17 at 13:46
3

I think this is the easies but also the cleanest way to assign a xib to a UIView. Xcode 7.3 and swift 2.0.

import UIKit

//Create CustomView class
class CustomView: UIView {

    class func instanceFromNib() -> UIView {
        return UINib(nibName: "CustomView", bundle: nil).instantiateWithOwner(nil, options: nil)[0] as! UIView
    }
}

//Use it
let customView = CustomView.instanceFromNib() as! CustomView
0xRLA
  • 3,279
  • 4
  • 30
  • 42
  • Please [edit] with more information. Code-only and "try this" answers are [discouraged](//meta.stackexchange.com/questions/196187), because they contain no searchable content, and don't explain why someone should "try this". We make an effort here to be a resource for knowledge. – Mogsdad May 06 '16 at 22:08
1

The true Swift approach is the use of protocols and protocol extensions.

I use it like this: To start I create a protocol

    protocol XibInitializable {
        static var name: String { get }
        static var bundle: Bundle? { get }

        static func fromXib() -> Self
    }

then I make a default implementation of this protocol use protocol extention

extension XibInitializable where Self : UIView {
    static var name: String {
        return String(describing: Self.self)
    }

    static var bundle: Bundle? {
        return nil
    }

    static func fromXib() -> Self {
        return UINib(nibName: name, bundle: bundle).instantiate(withOwner: nil, options: nil)[0] as! Self
    }
}

the implementation of our protocol is now complete

In order for this protocol to work, you need the name of our xib file and the class were the same. For example, for example

enter image description here

finally add the protocol and make your class "final", like here.

enter image description here

That's it

and use

enter image description here

Beslan Tularov
  • 3,111
  • 1
  • 21
  • 34
  • The only thing I'd change is to move the `bundle` property into the function as a parameter that defaults to `nil`. I'd also use `nibName` instead of `name` – jjatie Aug 03 '18 at 15:37
0

instead of adding an extension to UIView, you could define a protocol and add the implementation to a protocol extension. You can then declare that UIView conforms to the protocol.

This allows the return type to be Self instead of UIView. So the caller doesn't have to cast to the class.

Explained here: https://stackoverflow.com/a/33424509/845027

import UIKit

protocol UIViewLoading {}
extension UIView : UIViewLoading {}

extension UIViewLoading where Self : UIView {

  // note that this method returns an instance of type `Self`, rather than UIView
  static func loadFromNib() -> Self {
    let nibName = "\(self)".characters.split{$0 == "."}.map(String.init).last!
    let nib = UINib(nibName: nibName, bundle: nil)
    return nib.instantiateWithOwner(self, options: nil).first as! Self
  }

}
Community
  • 1
  • 1
Sam
  • 5,892
  • 1
  • 25
  • 27
0

Just made a UINib extension to load a view from xib and embed into a container view using constraints, using generics and strong naming (without using Strings, assuming you have the same file name for xib and implementation):

extension UINib {

    static func instantiateViewAndEmbedWithConstraints <T: UIView> (viewType viewType: T.Type, embedInto containerView: UIView) -> T {
        let view = UINib(nibName: String(viewType), bundle: nil).instantiateWithOwner(nil, options: nil).first as! T
        containerView.addSubview(view)
        view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint(item: view, attribute: .Leading, relatedBy: .Equal, toItem: containerView, attribute: .Leading, multiplier: 1, constant: 0).active = true
        NSLayoutConstraint(item: view, attribute: .Trailing, relatedBy: .Equal, toItem: containerView, attribute: .Trailing, multiplier: 1, constant: 0).active = true
        NSLayoutConstraint(item: view, attribute: .Top, relatedBy: .Equal, toItem: containerView, attribute: .Top, multiplier: 1, constant: 0).active = true
        NSLayoutConstraint(item: view, attribute: .Bottom, relatedBy: .Equal, toItem: containerView, attribute: .Bottom, multiplier: 1, constant: 0).active = true
        return view
    }

}

Usage:

...outlets...
@IBOutlet var containerView: UIView!
var customView: CustomView!

...viewDidLoad...
customView = UINib.instantiateViewAndEmbedWithConstraints(viewType: CustomView.self, embedInto: containerView)
rshev
  • 4,086
  • 1
  • 23
  • 32
0

Just subclass this simple class (swift 5):

open class NibView: UIView {

    open override func awakeAfter(using coder: NSCoder) -> Any? {
        if subviews.count == 0 {
            return UINib(nibName: "\(Self.self)", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
        }
        return self
    }
}

class CustomView: NibView {
}

As others pointed out, set File's Owner to your CustomView class (not xib's root view itself). Then set custom class to CustomView to any view that you want to be replaced by your custom view class. Also, autolayout respects all constraints inside your xib, at least as a subview of a UITableViewCell's content view. Not sure about other cases.

As simple, as it happens to be, somehow Apple did another quest for us for such a basic thing! What a wonderful company! Never gonna be bored with them!

m8labs
  • 3,671
  • 2
  • 30
  • 32