Unable to programatically create a UIViewController in Swift

11,107

Solution 1

As you've pointed out: As long as the nib name matches the class name in objective-C, even if you don't specify a nib name when initializing the view controller, the view controller will still look for the nib file whose name matches the name of the view controller class.

But for some reason (perhaps it's a bug), this is not the case in Swift.

Instead of writing:

let vc = ImageViewController()

You have to explicitly specify an interface when initializing the view controller:

let vc = ImageViewController(nibName: "nibName", bundle: nil)

Solution 2

As a convenience, I came up with this workaround so that I don't have to use the longer initializer everywhere. I have a BaseViewController that all my controllers extend.

init () {
    let className = NSStringFromClass(self.dynamicType).componentsSeparatedByString(".").last
    super.init(nibName: className, bundle: NSBundle(forClass: self.dynamicType))
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

Note, using NSBundle(forClass: self.dynamicType) takes care of namespacing in different targets.

Then when I want to instantiate a ViewController, I use MyViewController() as you could in Objective-C.

Share:
11,107
cfischer
Author by

cfischer

Updated on June 11, 2022

Comments

  • cfischer
    cfischer almost 2 years

    When I try to create an instance of a UIViewController in Swift, all the inherited initialisers are unavailable, even though I didn't define any designated inits in the view controller (or anything else, FWIW).

    Also, if I try to display it by making it the root view controller, it never gets displayed:

    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
            // Override point for customization after application launch.
    
            window = UIWindow(frame: UIScreen.mainScreen().bounds)
            window?.makeKeyAndVisible()
            window?.backgroundColor = UIColor.greenColor()
    
    
    
            let vc = ImageViewController()
            window?.rootViewController = vc
    
            return true
        }
    

    The code for the view controller is just Xcode's template:

    import UIKit
    
    class ImageViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            // Do any additional setup after loading the view.
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
    
        /*
        // MARK: - Navigation
    
        // In a storyboard-based application, you will often want to do a little preparation before navigation
        override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
            // Get the new view controller using segue.destinationViewController.
            // Pass the selected object to the new view controller.
        }
        */
    
    }
    

    Anybody knows what's going on????

  • cfischer
    cfischer over 9 years
    OK, now it kinda works. :-) If I call the nibName:,bundle: initializer explicitly (even though autocomplete won't recognise it) AND I pass the name of the xib file, it works. However, that's not the expected behaviour: If I just call init or init(nibName:nil, bundle:nil) it should pick up the name of the xib file by looking at the name of the class. And it doesn't. :-(
  • Lyndsey Scott
    Lyndsey Scott over 9 years
    @cfisher But where is your init?
  • cfischer
    cfischer over 9 years
    I didn't define ANY inits in the view controller. The code is exactly the one provided by Xcode's template. It's the one included at the end of the question. I'm calling it from my ApplicationDelegate, in the application did finish loading with options method.
  • Lyndsey Scott
    Lyndsey Scott over 9 years
    @cfisher OK, then that's your problem. No matter what, whether it's in your app delegate or in a custom init you 100% have to explicitly pass/include the name of your xib file. It's not true that "it should pick up the name of the xib file by looking at the name of the class." Even Apple lists the above method as the "designated initialized for the view controller class" in their docs: developer.apple.com/library/ios/Documentation/UIKit/Referenc‌​e/…:
  • cfischer
    cfischer over 9 years
    init(nibName nibName: String?, bundle nibBundle: NSBundle?) is indeed the designate initialisers for UIViewControllers. However, you do NOT have to specify a name for the nib file, unless the name of the nib file is different from the name of the class.
  • cfischer
    cfischer over 9 years
    From the Apple Docs: "However, if you do not specify a nib name, and do not override the loadView method in your custom subclass, the view controller searches for a nib file using other means. Specifically, it looks for a nib file with an appropriate name (without the .nib extension) and loads that nib file whenever its view is requested. " developer.apple.com/library/ios/Documentation/UIKit/Referenc‌​e/…
  • cfischer
    cfischer over 9 years
    That code, if written in Objective C, compiles and runs fine. So there's something weird going on when using Swift...
  • Lyndsey Scott
    Lyndsey Scott over 9 years
    @cfisher Hm... I'm looking into it.
  • Lyndsey Scott
    Lyndsey Scott over 9 years
    @cfisher Yeah, I've gotten the same results with my own code and have updated my answer accordingly for future answer seekers. Perhaps you should submit a bug report to Apple.
  • cfischer
    cfischer over 9 years
    Thanks anyway, you gave me at least a workaround! :-)