47

I'm creating a custom navigation controller. I have something like this:

public class CustomNavigationController: UINavigationController {

    // MARK: - Life Cycle

    override init(rootViewController: UIViewController) {
        super.init(rootViewController: rootViewController)

        delegate = self
    }

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        delegate = self
    }
}

I wanted to test this out so I created a CustomNavigationController like this:

CustomNavigationController(rootViewController: ViewController())

When I run the app I get this:

fatal error: use of unimplemented initializer 'init(nibName:bundle:)' for class 'TestApp.CustomNavigationController'

I don't see the problem, can anyone help me out?

user1007522
  • 7,858
  • 17
  • 69
  • 113

3 Answers3

55

UINavigationController's implementation of init(rootViewController:) probably calls self.init(nibName:bundle:) which you haven't implemented so it throws the error.

You should override init(nibName:bundle) in addition to the initializers you already override. init(nibName:bundle:) is a designated initializer while init(rootViewController:) is a convenience initializer.

dan
  • 9,695
  • 1
  • 42
  • 40
  • 3
    init(rootViewController:) cannot be a convenience initializer. As stated in the initializer delegation rules (listed in @Jack's answer below), a designated initializer can only call up to another designated initializer. It should be a compiler error if you attempt to call a superclass convenience initializer from a designated subclass initializer. – Cognitio Jan 07 '18 at 05:05
  • Thanks.. Helps me a lot in my keyboard extension :) – Anjali jariwala Aug 22 '18 at 06:39
  • 2
    This crash is suddenly appeared for iOS12 users. Although empty initialiser doesn't make any sense, it fixes that crash. – SoftDesigner Apr 03 '21 at 07:37
34

While using custom navigation controller, we need to use override initproperty of NavigationController as-

class CustomNavigationController: UINavigationController {

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

    }
    override init(rootViewController: UIViewController) {
        super.init(rootViewController: rootViewController)
    }
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }

}

& in Appdelegate class use -

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        self.window = UIWindow(frame: UIScreen.main.bounds)
        let vc = ViewController(nibName: "ViewController", bundle: nil)
        let navi =  CustomNavigationController(rootViewController: vc)
        window?.backgroundColor = .white
        window?.rootViewController = navi
        window?.makeKeyAndVisible()
        return true
    }
}

As per Apple Document - To simplify the relationships between designated and convenience initializers, Swift applies the following three rules for delegation calls between initializers:

Rule 1 A designated initializer must call a designated initializer from its immediate superclass.

Rule 2 A convenience initializer must call another initializer from the same class.

Rule 3 A convenience initializer must ultimately call a designated initializer.

A simple way to remember this is:-

enter image description here

Jack
  • 13,571
  • 6
  • 76
  • 98
12

I had this issue happen to users on iOS 12.4, so to do a workaround in my custom initializer I did something like this:

init(rootViewController: UIViewController, numberOfPages: Int) {
    self.numberOfPages = numberOfPages
    if #available(iOS 13.0, *) {
        super.init(rootViewController: rootViewController)
    } else {
        super.init(nibName: nil, bundle: nil)
        self.viewControllers = [rootViewController]
    }
}

I basically initialize my class differently for every iOS version below 13. This does seem like the shortest solution.

Repose
  • 2,157
  • 1
  • 24
  • 26
  • This solution works well if your subclass has properties needs to be initialized before calling `super.init(nibName: nil, bundle: nil)` – Honghao Z Jul 31 '21 at 20:05