Load a Uiview from Nib in Swift

Load a UIView from nib in Swift

Original Solution

  1. I created a XIB and a class named SomeView (used the same name for
    convenience and readability). I based both on a UIView.
  2. In the XIB, I changed the "File's Owner" class to SomeView (in the identity inspector).
  3. I created a UIView outlet in SomeView.swift, linking it to the top level view in the XIB file (named it "view" for convenience). I then added other outlets to other controls in the XIB file as needed.
  4. in SomeView.swift, I loaded the XIB inside the "init with code" initializer. There is no need to assign anything to "self". As soon as the XIB is loaded, all outlets are connected, including the top level view. The only thing missing, is to add the top view to the view hierarchy:

.

class SomeView: UIView {
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
NSBundle.mainBundle().loadNibNamed("SomeView", owner: self, options: nil)
self.addSubview(self.view); // adding the top level view to the view hierarchy
}
...
}

Note that this way I get a class that loads itself from nib. I could then use SomeView as a class whenever UIView could be used in the project (in interface builder or programmatically).

Update - using Swift 3 syntax

Loading a xib in the following extension is written as an instance method, which can then be used by an initializer like the one above:

extension UIView {

@discardableResult // 1
func fromNib<T : UIView>() -> T? { // 2
guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else { // 3
// xib not loaded, or its top view is of the wrong type
return nil
}
self.addSubview(contentView) // 4
contentView.translatesAutoresizingMaskIntoConstraints = false // 5
contentView.layoutAttachAll(to: self) // 6
return contentView // 7
}
}
  1. Using a discardable return value since the returned view is mostly of no interest to caller when all outlets are already connected.
  2. This is a generic method that returns an optional object of type UIView. If it fails to load the view, it returns nil.
  3. Attempting to load a XIB file with the same name as the current class instance. If that fails, nil is returned.
  4. Adding the top level view to the view hierarchy.
  5. This line assumes we're using constraints to layout the view.
  6. This method adds top, bottom, leading & trailing constraints - attaching the view to "self" on all sides (See: https://stackoverflow.com/a/46279424/2274829 for details)
  7. Returning the top level view

And the caller method might look like this:

final class SomeView: UIView {   // 1.
required init?(coder aDecoder: NSCoder) { // 2 - storyboard initializer
super.init(coder: aDecoder)
fromNib() // 5.
}
init() { // 3 - programmatic initializer
super.init(frame: CGRect.zero) // 4.
fromNib() // 6.
}
// other methods ...
}
  1. SomeClass is a UIView subclass that loads its content from a SomeClass.xib file. The "final" keyword is optional.
  2. An initializer for when the view is used in a storyboard (remember to use SomeClass as the custom class of your storyboard view).
  3. An initializer for when the view is created programmatically (i.e.: "let myView = SomeView()").
  4. Using an all-zeros frame since this view is laid out using auto-layout.
    Note that an "init(frame: CGRect) {..}" method is not created independently, since auto-layout is used exclusively in our project.
  5. & 6. Loading the xib file using the extension.

Credit: Using a generic extension in this solution was inspired by Robert's answer below.

Edit
Changing "view" to "contentView" to avoid confusion. Also changed the array subscript to ".first".

Loading a XIB file to a UIView Swift

I uses this in one of our projects, might be useful to you

import UIKit

class RegisterPageView: UIView {

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

What is the correct way to load view from xib?

One way to reuse a complex set of subviews is to define an embedded view controller. You start by defining your top-level view controller. In it, you define an outlet and connected it to an instance of your sub-controller (also defined in the nib). You also connect the sub-controller's view to a placeholder UIView in the top-level nib's view hierarchy.

class ViewController: UIViewController {

@IBOutlet var childController: ReusableViewController?

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}

The slight-of-hand occurs in the sub-controller. Its awakeFromNib function gets invoked when the super-controller is loaded. The sub-controller then uses the "placeholder" UIView it is connected to to insert it's view hierarchy into the top-level views.

class ReusableViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}

override func awakeFromNib() {
super.awakeFromNib()
// This controller manages a reusable view defined by a seperate nib.
// When defined in the parent nib, its view is connected to a placeholder view object.
// When the nib is loaded, the secondary nib is loaded and replaces the placeholder.
let placeholder = self.view!
Bundle.main.loadNibNamed("ReusableViewController", owner: self, options: nil)
// (the nib connects the view property to the actual view, replacing the placeholder)
placeholder.superview?.insertSubview(self.view, aboveSubview: placeholder)
// (do something about constraints here?)
placeholder.removeFromSuperview()
}

}

The advantage to this arrangement is that the sub-controller can have whatever bindings, outlets, actions, connections, properties, and business logic that it needs, neatly encapsulated and reusable.

This is a bit of a hack because it short-circuits the sub-controller's view-did-load lifecycle (because the controller never loads its own view). To address that, you could define another property that pointed the sub-controller at either a placeholder view or the container view that is should insert itself into. Then replace the Bundle.main.loadNibName(blah, blah, blah) stuff with just let replacement = self.view and let the view controller do all of the loading.


Here's a finished solution using storyboards contributed by the original poster after getting this to work

Using the reusable view in storyboard

Add a Container View (Not to confuse with View) to ViewController (your main view on storyboard), and set it's scene's (controller) class to the controller we just defined (ReusableViewController).

gif

That's it. This way you can add as many subviews to a view without having to wrap your subviews in storyboards as well as in code.

Container View's intended use is actually documented as exactly for this purpose here

Correct way to load a Nib for a UIView subclass

MyViewClass *myViewObject = [[[NSBundle mainBundle] loadNibNamed:@"MyViewClassNib" owner:self options:nil] objectAtIndex:0]

I'm using this to initialise the reusable custom views I have.


Note that you can use "firstObject" at the end there, it's a little cleaner. "firstObject" is a handy method for NSArray and NSMutableArray.

Here's a typical example, of loading a xib to use as a table header. In your file YourClass.m

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section {
return [[NSBundle mainBundle] loadNibNamed:@"TopArea" owner:self options:nil].firstObject;
}

Normally, in the TopArea.xib, you would click on File Owner and set the file owner to YourClass. Then actually in YourClass.h you would have IBOutlet properties. In TopArea.xib, you can drag controls to those outlets.

Don't forget that in TopArea.xib, you may have to click on the View itself and drag that to some outlet, so you have control of it, if necessary. (A very worthwhile tip is that when you are doing this for table cell rows, you absolutely have to do that - you have to connect the view itself to the relevant property in your code.)

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

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
}

Loading a custom view with a nib causes constraints being removed

The reason your constraints are not working as expected is because in the commonInit function sets both the label and button to be the same size as the view is.

You should declare a UIView property and then replace the content of commonInit function:

var contentView: UIView!

private func commonInit() {
if let nib = Bundle.main.loadNibNamed("Custom", owner: self, options:nil)?.first as? UIView {
contentView = nib
contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
contentView.frame = bounds
addSubview(contentView)
}
}

How to load a UIView from a NIB?

Yes, just call

[[NSBundle mainBundle] loadNibNamed:@"viewNib" owner:self options:nil];

You normally do this from the view controller you have set as File's Owner in the NIB. That way, you can declare an outlet for the view in the view controller which will automatically get connected when you load the NIB file. You don't even have to work with the return value of the method in this case.



Related Topics



Leave a reply



Submit