Stephen Fox
Stephen Fox

Reputation: 14470

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

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) {} ?? 
}

Upvotes: 164

Views: 219185

Answers (11)

yoAlex5
yoAlex5

Reputation: 34441

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)

Upvotes: 2

Radu Ursache
Radu Ursache

Reputation: 1509

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

Upvotes: 6

Jeremy
Jeremy

Reputation: 3149

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!"

Upvotes: 30

Pradeep Reddy Kypa
Pradeep Reddy Kypa

Reputation: 4022

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)

Upvotes: 2

Mahmood S
Mahmood S

Reputation: 421

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

Upvotes: 42

Frederik Winkelsdorf
Frederik Winkelsdorf

Reputation: 4573

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

Upvotes: 91

Ezimet
Ezimet

Reputation: 5078

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
}

Upvotes: 295

axunic
axunic

Reputation: 2316

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.")
    

Upvotes: 33

Madan Kumawat
Madan Kumawat

Reputation: 17

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)

        })
    })


}

Upvotes: -7

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))
    }
}

Upvotes: 33

Sam
Sam

Reputation: 5892

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
  }

}

Upvotes: 79

Related Questions