Loading ViewController from xib file
Asked Answered
A

14

68

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?

Asymptotic answered 5/5, 2016 at 9:5 Comment(4)
can you see the full codeBabyblueeyes
@Asymptotic 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.Lawgiver
bluenowhere: did you find your answer?Pademelon
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.Spiral
F
112

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)
Fadiman answered 21/6, 2017 at 12:14 Comment(3)
@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 :-)Readiness
if your vc name is same as xib file you can use (nibName: nil, bundle: nil) and it will workStainless
I get error loaded the "..." nib but the view outlet was not set.Stray
S
41
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()
Secure answered 22/4, 2019 at 21:18 Comment(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.Dingus
Of course. The name of the nib file needs to be the same as the name of the class.Secure
L
22

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.

Lauder answered 5/5, 2016 at 10:7 Comment(0)
T
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)
        }
Tarlatan answered 19/10, 2017 at 14:13 Comment(0)
P
3

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()
Pad answered 1/9, 2021 at 8:8 Comment(0)
M
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 :)

Manuelmanuela answered 5/5, 2016 at 9:28 Comment(0)
G
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.

Glossal answered 5/5, 2016 at 9:24 Comment(2)
Please don't just put code explain what was mistake and how your code helps to solve it :)Hawthorne
Yeah, as you can see the different skill for writing code. Some time it won't allow to initialize within one lines of code.Glossal
L
1

Updated for Swift 5

        let myVC = Bundle.main.loadNibNamed("MyViewController", owner: self, options: nil)![0] as? MyViewController
Laurenelaurens answered 29/3, 2020 at 12:52 Comment(1)
It is returning a nil value for myVC, something is missing in the code, try to add a full code snippet instead :)Serilda
S
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.")
    }
}
Soria answered 13/6, 2020 at 1:11 Comment(1)
ViewControllerInXib doesn't exists.Penurious
I
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.

Inkblot answered 24/7, 2019 at 9:36 Comment(0)
D
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()
}

}

Deceive answered 22/4, 2021 at 3:1 Comment(0)
M
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.

Melanymelaphyre answered 20/5, 2021 at 13:3 Comment(0)
P
-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)
Panarabism answered 16/4, 2020 at 6:15 Comment(1)
follow #47383066 for more information.Panarabism
C
-4

Works this in Swift5

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

Carrycarryall answered 20/1, 2021 at 7:31 Comment(1)
No difference using Swift 5 compared to the previous version. This is the same as the other answers.Botello

© 2022 - 2024 — McMap. All rights reserved.