How to initialize/instantiate a custom UIView class with a XIB file in Swift
Asked Answered
M

11

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) {} ?? 
}
Manado answered 26/8, 2014 at 18:57 Comment(1)
refer full source code sample for iOS9 Swift 2.0 github.com/karthikprabhuA/CustomXIBSwift and related thread #24858486Holton
A
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
}
Aberrant answered 26/8, 2014 at 19:19 Comment(12)
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 :)Pickax
In my case view initialised later! if it is self.view.addSubview(view) , view should be var view = MyClass.instanceFromNib()Aberrant
Ahhh, @Aberrant - I see. Very interesting approach!Pickax
Is there a way to animate this process? I have it working I just want it to act almost like a segueLoupgarou
@Aberrant 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.?Kaitlinkaitlyn
Should it return "MyClass" instead of just UIView?Adulation
@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.Aberrant
@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.Camphor
How Can this be done in Obj C? var view = MyClass.instanceFromNib self.view.addSubview(view())Wappes
IBoutlets are not working in this approach... I'm getting: "this class is not key value coding-compliant for the key"Minify
@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.Aberrant
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.Puli
U
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

Ulyanovsk answered 5/4, 2016 at 11:15 Comment(10)
@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!Ulyanovsk
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.Estes
@Estes 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.Ulyanovsk
As mentioned "connect Outlets to File Owner and set File Owner class to your own class." connect the outlets to the File OwnerVaticination
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?Subspecies
@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.Ulyanovsk
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?Subspecies
@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: 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!Ulyanovsk
@Estes to avoid the infinite loop you have to set the class name to File's OwnerDelorisdelorme
works as of iOS 14.4 and xcode 12.5. this is the easiest way to init xibsEligibility
G
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
  }

}
Gautier answered 29/10, 2015 at 21:0 Comment(8)
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.Heroin
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! myViewTypeMisnomer
@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.Theatricalize
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 XC8Balenciaga
@Balenciaga 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.Theatricalize
You can also get the name of the class using let nibName = String(describing: Self.self) (Swift 3)Goneness
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?Depreciable
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 github.com/theappbusiness/TABSwiftLayoutGautier
W
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
    }
}
Weightless answered 14/10, 2016 at 21:2 Comment(1)
One line saved my day which is view.translatesAutoresizingMaskIntoConstraints = true. Thank you :)Operand
T
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))
    }
}
Thee answered 20/10, 2016 at 13:9 Comment(2)
Best answer to me as the output view as the Type of the UIView subclassScharff
This is an extension to Bundle but it assumes to load always from main bundle. Can not be usable in Unit Tests at all.Darfur
S
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.")
    
Severson answered 16/10, 2018 at 9:2 Comment(3)
if there are buttons in custom view, how can we handle their actions in different view controllers?Noma
you can use protocol-delegate. take a look here #29603112.Severson
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"Halden
K
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!"
Kovar answered 9/6, 2017 at 22:54 Comment(1)
I get back "loaded the "MyClas" nib but the view outlet was not set.'" if owner is not setCaddric
E
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

Eligibility answered 19/3, 2021 at 10:49 Comment(1)
key point of this view.translatesAutoresizingMaskIntoConstraints = trueMcgrody
L
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)
Languor answered 6/10, 2020 at 16:23 Comment(0)
H
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)
Hyperdulia answered 31/7, 2022 at 19:1 Comment(0)
I
-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)

        })
    })


}
Imbibe answered 14/6, 2017 at 6:8 Comment(2)
bad formatted code, no explaination, hence the downvote.Valine
What does all this have to do with the question?Operable

© 2022 - 2024 — McMap. All rights reserved.