Reputation: 2733
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?
Upvotes: 69
Views: 109137
Reputation: 61
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()
Upvotes: 3
Reputation: 4330
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.
Upvotes: 0
Reputation: 1
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()
}
}
Upvotes: 0
Reputation: 9
Works this in Swift5
self.navigationController!.pushViewController(MyViewController(nibName: "MyViewController", bundle: nil), animated: true)
Upvotes: -4
Reputation: 16361
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.")
}
}
Upvotes: 1
Reputation: 1019
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)
Upvotes: -1
Reputation: 1184
Updated for Swift 5
let myVC = Bundle.main.loadNibNamed("MyViewController", owner: self, options: nil)![0] as? MyViewController
Upvotes: 1
Reputation: 18428
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.
Upvotes: 22
Reputation: 4115
@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.
Upvotes: 0
Reputation: 1203
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()
Upvotes: 41
Reputation: 109
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)
}
Upvotes: 4
Reputation: 3489
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)
Upvotes: 113
Reputation: 1158
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...
the above button is connected to controller now but when i comment outlet
so try to find outlet(xxx) that is missing from viewcontroller but is in xib file.Hope it helps :)
Upvotes: 2
Reputation: 3247
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.
Upvotes: 1