164

I have a class called MyClass which is a subclass of UIView, that I want to initialize with a XIB file. I am not sure how to initialize this class with the xib file called View.xib

class MyClass: UIView {

    // what should I do here? 
    //init(coder aDecoder: NSCoder) {} ?? 
}
Zonily Jame
  • 5,053
  • 3
  • 30
  • 56
Stephen Fox
  • 14,190
  • 19
  • 48
  • 52
  • 5
    refer full source code sample for iOS9 Swift 2.0 https://github.com/karthikprabhuA/CustomXIBSwift and related thread http://stackoverflow.com/questions/24857986/load-a-uiview-from-nib-in-swift/31957247#31957247 – karthikPrabhu Alagu Aug 12 '15 at 06:12

11 Answers11

295

I tested this code and it works great:

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

Initialise the view and use it like below:

var view = MyClass.instanceFromNib()
self.view.addSubview(view)

OR

var view = MyClass.instanceFromNib
self.view.addSubview(view())

UPDATE Swift >=3.x & Swift >=4.x

class func instanceFromNib() -> UIView {
    return UINib(nibName: "nib file name", bundle: nil).instantiate(withOwner: nil, options: nil)[0] as! UIView
}
shim
  • 9,289
  • 12
  • 69
  • 108
Ezimet
  • 5,058
  • 4
  • 23
  • 29
  • 1
    It should be `var view = MyClass.instanceFromNib()` & `self.view.addSubview(view)` as opposed to `var view = MyClass.instanceFromNib` & `self.view.addSubview(view())`. Just a small suggestion to improve the answer :) – Logan Aug 26 '14 at 19:34
  • 1
    In my case view initialised later! if it is self.view.addSubview(view) , view should be var view = MyClass.instanceFromNib() – Ezimet Aug 26 '14 at 19:39
  • Ahhh, @Ezimet - I see. Very interesting approach! – Logan Aug 26 '14 at 20:55
  • Is there a way to animate this process? I have it working I just want it to act almost like a segue – Dom Bryan Aug 03 '15 at 11:07
  • 2
    @Ezimet what about the IBActions inside that view. where to handle them. Like I have a button in my view (xib) how to handle the IBAction click event of that button.? – Qadir Hussain Feb 21 '17 at 09:46
  • 9
    Should it return "MyClass" instead of just UIView? – Kesong Xie Apr 24 '17 at 01:22
  • @Vojta I did not say you have to typecast and implement it for every subclass, This is the most basic way to accomplish the task. It is not the only way, you can extend it any way you like and choose the best method that suits you. – Ezimet May 24 '18 at 12:39
  • @Ezimet: I didn't mean it offensive, I just want to help the community by topping the best answer that is currently available. I definitely appreciate your effort for offering the answer. – Vojta May 24 '18 at 13:33
  • How Can this be done in Obj C? var view = MyClass.instanceFromNib self.view.addSubview(view()) – Laxy Jun 20 '18 at 19:18
  • 10
    IBoutlets are not working in this approach... I'm getting: "this class is not key value coding-compliant for the key" – Radek Wilczak Aug 16 '18 at 14:05
  • @Radek Wilczak The error you are getting is nothing to do with this approach, search google for answer or raise a question on different post. – Ezimet Aug 16 '18 at 14:21
  • self.view.addSubview(view) probably should not work because UIView has no member 'view' except we created a variable called view and then added itself to itself as a subView. Crash much? I think the statement should be self.addSubview(view). Also, I wish someone would just say what we all are probably thinking, "That this is the biggest kluge ever!" I wish there were an answer that instanciated Self instead of making it a subView of Self. – Sojourner9 Dec 12 '18 at 16:15
91

Sam's solution is already great, despite it doesn't take different bundles into account (NSBundle:forClass comes to the rescue) and requires manual loading, a.k.a typing code.

If you want full support for your Xib Outlets, different Bundles (use in frameworks!) and get a nice preview in Storyboard try this:

// NibLoadingView.swift
import UIKit

/* Usage: 
- Subclass your UIView from NibLoadView to automatically load an Xib with the same name as your class
- Set the class name to File's Owner in the Xib file
*/

@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet weak var view: UIView!

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

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

    private func nibSetup() {
        backgroundColor = .clearColor()

        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(view)
    }

    private func loadViewFromNib() -> UIView {
        let bundle = NSBundle(forClass: self.dynamicType)
        let nib = UINib(nibName: String(self.dynamicType), bundle: bundle)
        let nibView = nib.instantiateWithOwner(self, options: nil).first as! UIView

        return nibView
    }

}

Use your xib as usual, i.e. connect Outlets to File Owner and set File Owner class to your own class.

Usage: Just subclass your own View class from NibLoadingView & Set the class name to File's Owner in the Xib file

No additional code required anymore.

Credits where credit's due: Forked this with minor changes from DenHeadless on GH. My Gist: https://gist.github.com/winkelsdorf/16c481f274134718946328b6e2c9a4d8

Dania Delbani
  • 816
  • 1
  • 11
  • 27
Frederik Winkelsdorf
  • 4,383
  • 1
  • 34
  • 42
  • @fengd I don't know, but it's just a little convenience helper. Loading manually is of course easy, too. But I like helpers to save time with recurring tasks :) Thanks for the up vote! – Frederik Winkelsdorf Apr 25 '16 at 14:08
  • 9
    This solution brakes outlet connections (because it adds the loaded view as a subview) and calling `nibSetup` from `init?(coder:)` will cause infinite recursion if embedding `NibLoadingView` in a XIB. – redent84 Sep 06 '16 at 09:00
  • 3
    @redent84 Thanks for your comment and the downvote. If you have a 2nd look, it should replace the previous SubView (no new instance variable has been given). You are right about the infinite recursion, that should be omitted if struggling with IB. – Frederik Winkelsdorf Sep 07 '16 at 15:45
  • 1
    As mentioned "connect Outlets to File Owner and set File Owner class to your own class." connect the outlets to the File Owner – Yusuf X Sep 08 '16 at 23:39
  • 2
    I am always uncomfortable about using this method to load view from xib. We are basically adding a view which is a subclass of Class A, in a view which is also subclass of Class A. Isn't there some way to prevent this iteration? – Prajeet Shrestha Jan 20 '17 at 13:49
  • @PrajeetShrestha That shouldn't be the case. We are instantiating the first UIView of the xib (usually contentView) and add this as subView. We are not using the class as UIView class in the xib but connect the outlets via File Owner. But agreed against the pro/cons using this method. That is still discussable, but it found it easy at that time (Apr 2015). Today I would likely use a protocol based approach. – Frederik Winkelsdorf Jan 23 '17 at 16:57
  • If I subclass Class B from NibLoadingView, and in some storyboard, if I put UIView and change it's class to ClassB from storyboard and give it's outlet to some Controller as viewB, now if I want to change background of viewB, I can't directly change it. I need to do viewB.view.backgroundColor = UIColor.black. ------ Also what is the protocol based approach, can you share me some link for it? – Prajeet Shrestha Jan 23 '17 at 17:03
  • 2
    @PrajeetShrestha That is likely due to nibSetup() overriding the Background color to `.clearColor()` - after loading it from the Storyboard. But it should work if you do it by code after it's instantiated. Anyway, as said an even more elegant approach is the protocol based one. So sure, here's a link for you: https://github.com/AliSoftware/Reusable. I am using a similar approach now regarding the UITableViewCells (which I implemented before I discovered that really useful project). hth! – Frederik Winkelsdorf Jan 23 '17 at 19:48
  • @redent84 to avoid the infinite loop you have to set the class name to File's Owner – Dania Delbani Mar 12 '20 at 10:05
  • 1
    works as of iOS 14.4 and xcode 12.5. **this** is the easiest way to init xibs – Radu Ursache Mar 19 '21 at 10:44
78

As of Swift 2.0, you can add a protocol extension. In my opinion, this is a better approach because the return type is Self rather than UIView, so the caller doesn't need to cast to the view class.

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
  }

}
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Sam
  • 5,892
  • 1
  • 25
  • 27
  • 4
    This is a better solution than the selected answer as there is no need for casting and it will also be reusable across any other UIView subclasses you create in the future. – user3344977 Nov 01 '15 at 21:23
  • 3
    I tried this with Swift 2.1 and Xcode 7.2.1. It worked some of the time and would hang up unpredictably at others with a mutex lock. The last two lines used directly in code worked every time with the last line modified to be `var myView = nib.instantiate... as! myViewType` – Paul Linsay Feb 24 '16 at 14:41
  • @jr-root-cs Your edit contained typos/errors, I had to roll it back. And anyway please don't add code to existing answers. Instead, make a comment; or add your version *in your own answer*. Thanks. – Eric Aya Sep 06 '16 at 08:46
  • I posted code which I had opened and tested in my project using `Swift 3` (XCode 8.0 beta 6) without problems. The typo was in `Swift 2`. Why should it be another answer when this answer is good and users will likely like search what are the changes when they will use XC8 – jr.root.cs Sep 06 '16 at 10:48
  • 1
    @jr.root.cs Yes this answer is good, that's why nobody should alter it. This is Sam's answer, not yours. If you want to comment on it, leave a comment; if you want to post a new/updated version, do it *in your own post*. Edits are meant to fix typos/indentation/tags not to add your versions in other people's posts. Thanks. – Eric Aya Sep 06 '16 at 11:42
  • 1
    You can also get the name of the class using `let nibName = String(describing: Self.self)` (Swift 3) – OliverD Nov 08 '16 at 18:01
  • As nice and compact as this solution is, i load it programmatically and then attempt to add constraints to no avail, is this my doing it incorrect or a short coming of this approach? – Genhain Feb 14 '17 at 04:06
  • i always use this approach and add constraints without issues. check you've activated your constraints. or try using an open source constraint simplification framework such as https://github.com/theappbusiness/TABSwiftLayout – Sam Feb 14 '17 at 06:50
42

And this is the answer of Frederik on Swift 3.0

/*
 Usage:
 - make your CustomeView class and inherit from this one
 - in your Xib file make the file owner is your CustomeView class
 - *Important* the root view in your Xib file must be of type UIView
 - link all outlets to the file owner
 */
@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet weak var view: UIView!

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

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

    private func nibSetup() {
        backgroundColor = .clear

        view = loadViewFromNib()
        view.frame = bounds
        view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(view)
    }

    private func loadViewFromNib() -> UIView {
        let bundle = Bundle(for: type(of: self))
        let nib = UINib(nibName: String(describing: type(of: self)), bundle: bundle)
        let nibView = nib.instantiate(withOwner: self, options: nil).first as! UIView

        return nibView
    }
}
Dania Delbani
  • 816
  • 1
  • 11
  • 27
Mahmood S
  • 421
  • 4
  • 3
33

Universal way of loading view from xib:

Example:

let myView = Bundle.loadView(fromNib: "MyView", withType: MyView.self)

Implementation:

extension Bundle {

    static func loadView<T>(fromNib name: String, withType type: T.Type) -> T {
        if let view = Bundle.main.loadNibNamed(name, owner: nil, options: nil)?.first as? T {
            return view
        }

        fatalError("Could not load view with type " + String(describing: type))
    }
}
MobileMon
  • 8,341
  • 5
  • 56
  • 75
33

Swift 4

Here in my case I have to pass data into that custom view, so I create static function to instantiate the view.

  1. Create UIView extension

    extension UIView {
        class func initFromNib<T: UIView>() -> T {
            return Bundle.main.loadNibNamed(String(describing: self), owner: nil, options: nil)?[0] as! T
        }
    }
    
  2. Create MyCustomView

    class MyCustomView: UIView {
    
        @IBOutlet weak var messageLabel: UILabel!
    
        static func instantiate(message: String) -> MyCustomView {
            let view: MyCustomView = initFromNib()
            view.messageLabel.text = message
            return view
        }
    }
    
  3. Set custom class to MyCustomView in .xib file. Connect outlet if necessary as usual. enter image description here

  4. Instantiate view

    let view = MyCustomView.instantiate(message: "Hello World.")
    
axunic
  • 2,256
  • 18
  • 18
  • if there are buttons in custom view, how can we handle their actions in different view controllers? – jayant rawat Jan 31 '20 at 07:45
  • you can use protocol-delegate. take a look here https://stackoverflow.com/questions/29602612/swift-creating-a-button-that-calls-a-method-in-another-class1/29603255. – axunic Jan 31 '20 at 08:05
  • It failed to load an image in xib, getting the following error. Could not load the "_IBBrokenImage_" image referenced from a nib in the bundle with identifier "test.Testing" – Ravi Raja Jangid Jun 13 '20 at 10:40
30

Swift 3 Answer: In my case, I wanted to have an outlet in my custom class that I could modify:

class MyClassView: UIView {
    @IBOutlet weak var myLabel: UILabel!
    
    class func createMyClassView() -> MyClassView {
        let myClassNib = UINib(nibName: "MyClass", bundle: nil)
        return myClassNib.instantiate(withOwner: nil, options: nil)[0] as! MyClassView
    }
}

When in the .xib file, make sure that the Custom Class field is MyClassView. Don't bother with the File's Owner.

Make sure Custom Class is MyClassView

Also, make sure that you connect the outlet in MyClassView to the label: Outlet for myLabel

To instantiate it:

let myClassView = MyClassView.createMyClassView()
myClassView.myLabel.text = "Hello World!"
Anuran Barman
  • 1,556
  • 2
  • 16
  • 31
Jeremy
  • 2,801
  • 2
  • 30
  • 31
6

Swift 5.3

Create a class named NibLoadingView with the following contents:

import UIKit

/* Usage:
- Subclass your UIView from NibLoadView to automatically load an Xib with the same name as your class
- Set the class name to File's Owner in the Xib file
*/

@IBDesignable
class NibLoadingView: UIView {

    @IBOutlet public weak var view: UIView!
    
    private var didLoad: Bool = false

    public override init(frame: CGRect) {
        super.init(frame: frame)
        
        self.nibSetup()
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        
        self.nibSetup()
    }
    
    open override func layoutSubviews() {
        super.layoutSubviews()
        
        if !self.didLoad {
            self.didLoad = true
            self.viewDidLoad()
        }
    }
    
    open func viewDidLoad() {
        self.setupUI()
    }

    private func nibSetup() {
        self.view = self.loadViewFromNib()
        self.view.frame = bounds
        self.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        self.view.translatesAutoresizingMaskIntoConstraints = true

        addSubview(self.view)
    }

    private func loadViewFromNib() -> UIView {
        guard let nibName = type(of: self).description().components(separatedBy: ".").last else {
            fatalError("Bad nib name")
        }
        
        if let defaultBundleView = UINib(nibName: nibName, bundle: Bundle(for: type(of: self))).instantiate(withOwner: self, options: nil).first as? UIView {
            return defaultBundleView
        } else {
            fatalError("Cannot load view from bundle")
        }
    }
}

Now create a XIB & UIView class pair, set XIB's owner to UIView class and subclass NibLoadingView.

You can now init the class just like ExampleView(), ExampleView(frame: CGRect), etc or directly from storyboards.

You can also use viewDidLoad just like in UIViewController. All your outlets and layouts are rendered in that moment.

Based on Frederik's answer

Radu Ursache
  • 1,230
  • 1
  • 22
  • 35
2

Below code will do the job if anyone wants to load a custom View with XIB Programmatically.

let customView = UINib(nibName:"CustomView",bundle:.main).instantiate(withOwner: nil, options: nil).first as! UIView
customView.frame = self.view.bounds
self.view.addSubview(customView)
Pradeep Reddy Kypa
  • 3,992
  • 7
  • 57
  • 75
2

Create a view from .xib

let nib = UINib(nibName: "View1", bundle: nil) //View1 is a file name(View1.swift)
if let view = nib.instantiate(withOwner: self, options: nil).first as? UIView {
    // logic
}
//or
if let view = Bundle.main.loadNibNamed("View1", owner: self, options: nil)?.first as? UIView {
    // logic
}

Since .xib can contains several view, that is why you are working with array here(.first)

For example

  1. Create View1.xib
  2. Create View1.swift where set owner(loadNibNamed()) in code to create the instance of class("View1")
  3. Set File's Owner in View1.xib as View1. Allows to connect outlets and actions
import UIKit

class View1: UIView {
    @IBOutlet var contentView: UIView!
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        self.commonInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        self.commonInit()
    }
    
    private func commonInit() {
        if let view = Bundle.main.loadNibNamed("View1", owner: self, options: nil)?.first as? UIView {
            addSubview(view)
            view.frame = self.bounds
        }
    }
}

Notes if we move Custom Class from File's owner to Container View we get error(loop). It is because of:

System init instance from Container View where we init it again in commonInit()

.loadNibNamed

Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ff7bf6fbfc8)
yoAlex5
  • 29,217
  • 8
  • 193
  • 205
-7
override func draw(_ rect: CGRect) 
{
    AlertView.layer.cornerRadius = 4
    AlertView.clipsToBounds = true

    btnOk.layer.cornerRadius = 4
    btnOk.clipsToBounds = true   
}

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

@IBAction func okBtnDidClicked(_ sender: Any) {

    removeAlertViewFromWindow()

    UIView.animate(withDuration: 0.4, delay: 0.0, options: .allowAnimatedContent, animations: {() -> Void in
        self.AlertView.transform = CGAffineTransform(scaleX: 0.1, y: 0.1)

    }, completion: {(finished: Bool) -> Void in
        self.AlertView.transform = CGAffineTransform.identity
        self.AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)
        self.AlertView.isHidden = true
        self.AlertView.alpha = 0.0

        self.alpha = 0.5
    })
}


func removeAlertViewFromWindow()
{
    for subview  in (appDel.window?.subviews)! {
        if subview.tag == 500500{
            subview.removeFromSuperview()
        }
    }
}


public func openAlertView(title:String , string : String ){

    lblTital.text  = title
    txtView.text  = string

    self.frame = CGRect(x: 0, y: 0, width: screenWidth, height: screenHeight)
    appDel.window!.addSubview(self)


    AlertView.alpha = 1.0
    AlertView.isHidden = false

    UIView.animate(withDuration: 0.2, animations: {() -> Void in
        self.alpha = 1.0
    })
    AlertView.transform = CGAffineTransform(scaleX: 0.0, y: 0.0)

    UIView.animate(withDuration: 0.3, delay: 0.2, options: .allowAnimatedContent, animations: {() -> Void in
        self.AlertView.transform = CGAffineTransform(scaleX: 1.1, y: 1.1)

    }, completion: {(finished: Bool) -> Void in
        UIView.animate(withDuration: 0.2, animations: {() -> Void in
            self.AlertView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)

        })
    })


}
Paul Karam
  • 4,052
  • 8
  • 30
  • 53