67

I had a MyViewController.swift and a MyViewController.xib presenting the layout of MyViewController.

I tried different methods to load this view controller including:

//1
let myVC = UINib(nibName: "MyViewController", bundle:
       nil).instantiateWithOwner(nil, options: nil)[0] as? MyViewController

//2
let myVC = NSBundle.mainBundle().loadNibNamed("MyViewController", owner: self, options: nil)[0] as? MyViewController

//3
let myVC = MyViewController(nibName: "MyViewController", bundle: nil)

The third one is the only successful initialisation, but the previous two are causing error:

Terminating app due to uncaught exception 'NSUnknownKeyException',

reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key XXX.

What's wrong with those loading methods?

bluenowhere
  • 2,683
  • 5
  • 24
  • 37
  • can you see the full code – Iyyappan Ravi May 05 '16 at 09:14
  • @bluenowhere Check all the outlets you have created and connected with components. Make sure there should not be any exclamation mark in the `Outlets` in `connection inspector` in your `.xib` file. – Vatsal K May 05 '16 at 10:30
  • bluenowhere: did you find your answer? – Boaz Frenkel Nov 18 '17 at 15:43
  • 1
    I believe the first two methods initialize your "MyViewController" class as a UIViewController and don't hook up the outlets and actions correctly, so the app crashes trying to find them. Initializing with your class' constructor hooks up the class outlets to the nib so everything works as it should. – Trev14 May 25 '18 at 14:10

14 Answers14

111

Swift 3

let myViewController = MyViewController(nibName: "MyViewController", bundle: nil)
self.present(myViewController, animated: true, completion: nil)

or push in navigation controller

self.navigationController!.pushViewController(MyViewController(nibName: "MyViewController", bundle: nil), animated: true)
Marcos Reboucas
  • 3,409
  • 1
  • 29
  • 35
  • 2
    @Woodstock: Kind of! It is the correct answer to the question 'How do I load *the layout of* a ViewController from a XIB file'. However, the original question is ambiguous as OP doesn't seem to distinguish between (1) loading the layout (View) of a programmatically instantiated ViewController from a XIB, and (2) loading the ViewController instance *itself* from the XIB. AechoLiu's answer is the correct answer to the second interpretation :-) – Chris Hatton Sep 01 '17 at 13:26
  • 4
    if your vc name is same as xib file you can use (nibName: nil, bundle: nil) and it will work – Adam Smaka Nov 24 '18 at 16:32
  • 3
    I get error `loaded the "..." nib but the view outlet was not set.` – JCutting8 Jan 13 '20 at 11:19
40
extension UIViewController {
    static func loadFromNib() -> Self {
        func instantiateFromNib<T: UIViewController>() -> T {
            return T.init(nibName: String(describing: T.self), bundle: nil)
        }

        return instantiateFromNib()
    }
}

Use it as the following:-

let testVC = TestVC.loadFromNib()
SamehDos
  • 1,183
  • 1
  • 10
  • 23
  • 2
    This is a great approach to avoid a bunch of duplicate code! You should note that the name of the nib file needs to be the same as the name of the class. – toddg Jun 12 '19 at 21:06
  • 1
    Of course. The name of the nib file needs to be the same as the name of the class. – SamehDos Jun 19 '19 at 07:21
21

File's Owner

Notice the File's Owner. In your case, the File's Owner must be MyViewController, or its sub-class.

And the following code, if it executes in class Foo.

// If `self` is an instance of `Foo` class.
// In this case, `File's Owner` will be a `Foo` instance due to the `self` parameter.
let myVC = NSBundle.mainBundle().loadNibNamed("MyViewController", owner: self, options: nil)[0] as? MyViewController

It assigns self as owner. So, the File's Owner is Foo, not MyViewController. Then, for Foo class, those IBOutlet cannot be connected to Foo. So, it throws exception.

Community
  • 1
  • 1
AechoLiu
  • 17,522
  • 9
  • 100
  • 118
4

I had the same problem. The automatically generated xib had a UIView in it. You have to delete the view, add new view controller to the xib, set the view controller class to the one you want and then connect the outlets. After all of this you can use the codes provided above to get an instance of this view controller, like this:

if let menuVC = Bundle.main.loadNibNamed("MenuViewController", owner: nil, options: nil)?.first as? MenuViewController {
            menuVC.profileType = profileType
            vc.present(menuVC, animated: true, completion: nil)
        }
Archangel
  • 99
  • 1
  • 6
2

The problem is not with the methods...you have probably kept an outlet(XXX) connected for some uielement and have removed it from corresponding controller...I am adding example below...enter image description here

the above button is connected to controller now but when i comment outlet enter image description here

my app crashes enter image description here

enter image description here

so try to find outlet(xxx) that is missing from viewcontroller but is in xib file.Hope it helps :)

Sanman
  • 1,158
  • 11
  • 23
2

You can use this small UIViewController extension

extension UIViewController {

    class func loadController() -> Self {
         return Self(nibName: String(describing: self), bundle: nil)
         //Or You can use this as well
         //Self.init(nibName: String(describing: self), bundle: nil)
    }
}

Use like this

let controller = CustomViewController.loadController()
1

Try below code,

//1

let nib = UINib(nibName: "MyViewController", bundle:nil)
myVC = nib.instantiateWithOwner(self, options: nil)[0] as? MyViewController

OR

myVC = nib.instantiateWithOwner(self, options: nil).first as? MyViewController

//2

let nib : NSArray = NSBundle.mainBundle().loadNibNamed("MyViewController", owner: self, options: nil)
myVC = nib.objectAtIndex(0) as? MyViewController

This will work.

Jigar Tarsariya
  • 3,189
  • 3
  • 14
  • 38
1

Updated for Swift 5

        let myVC = Bundle.main.loadNibNamed("MyViewController", owner: self, options: nil)![0] as? MyViewController
rswayz
  • 1,122
  • 2
  • 10
  • 21
1

Connect the UIButton with an @IBAction and add the following code to the action method to present a new UIViewController that is set up inside a .xib file.

@IBAction func handleButtonAction(_ sender: UIButton) {
    let viewControllerInXib = ViewControllerInXib(nibName: "ViewControllerXibName", bundle: nil)
    present(viewControllerInXib, animated: true)
}

To navigate via UINavigationController you should use this method:

@IBAction func handleButtonAction(_ sender: UIButton) {
    let viewControllerInXib = ViewControllerInXib(nibName: "ViewControllerXibName", bundle: nil)
    if let navigationController = navigationController {
        navigationController.pushViewController(viewControllerInXib, animated: true)
    } else {
        print("Navigation controller unavailable! Use present method.")
    }
}
Frankenstein
  • 15,732
  • 4
  • 22
  • 47
0

@AechoLiu's answer is great. I had the same question and answered it with the below fix.

Problem:

let vc1 = NSViewController(nibName: YDNibIdentifier.myplainvc, bundle: nil)

Fix:

let vc1 = MyPlainViewController(nibName: YDNibIdentifier.myplainvc, bundle: nil)

I had accidentally cast my Nib file to the wrong Clas ( NSViewController ), despite having it connected correctly inside the .xib file.

rustyMagnet
  • 3,479
  • 1
  • 31
  • 41
0
public extension UIViewController {
static func loadNib() -> Self {
    func instantiateFromNib<T: UIViewController>() -> T {
        return T.init(nibName: String(describing: T.self), bundle: Bundle.init(for: Self.self))
    }
    return instantiateFromNib()
}

}

0

This extension function didn't work for me.

static func loadFromNib() -> Self {
    func instantiateFromNib<T: UIViewController>() -> T {
        return T.init(nibName: String(describing: T.self), bundle: nil)
    }
    return instantiateFromNib()
}

It was throwing me

Could not load NIB in bundle: 'NSBundle ... with name 'UIViewController'

So, I changed it to this and got it working.

static func instantiateFromNib<T: UIViewController>() -> T {
    // It is going to return YourAppName.YourClassName
    let classDescription = classForCoder().description()
    // Replacing YourAppName with Empty string
    let nibFileName = classDescription.replacingOccurrences(of: "\(Bundle.main.infoDictionary?["CFBundleName"] as! String).", with: String())
    return T.init(nibName: nibFileName, bundle: Bundle.init(for: Self.self))
}

Just keep that in mind your .xib file and your .swift class name should be the same for it to work.

OhhhThatVarun
  • 3,981
  • 2
  • 26
  • 49
-1

I removed File owner’s class name and set that to the class name of first view. And then I had to set outlets to the components which I’m going to use.

Then I loaded that view class like

let myViewController = Bundle.main.loadNibNamed("MyViewController", owner: self, options: nil)?.first as! MyViewController
view.addSubview(myViewController)
Wimukthi Rajapaksha
  • 961
  • 1
  • 11
  • 23
  • follow https://stackoverflow.com/questions/47382566/could-not-cast-value-of-type-uiview-to-customview-using-xib/61243777#61243777 for more information. – Wimukthi Rajapaksha Apr 16 '20 at 06:16
-4

Works this in Swift5

self.navigationController!.pushViewController(MyViewController(nibName: "MyViewController", bundle: nil), animated: true)

emadhura
  • 9
  • 1
  • No difference using Swift 5 compared to the previous version. This is the same as the other answers. – Eric Aya Jan 20 '21 at 10:31