Every Swift developer runs into this error at some point.

Class 'ViewController' has no initializers

To resolve this error, you need to understand an important aspect of classes. Swift requires that the stored properties of a class receive an initial value during initialization. That includes the stored properties the class inherits from its superclass.

Swift requires that the stored properties of a class receive an initial value during initialization.

Take a look at this example. We define a class with name ViewController, a UIViewController subclass. The ViewController class declares a stored property, note, of type Note. The note property is a constant and it doesn't have an initial value.

import UIKit

internal final class ViewController: UIViewController {

    // MARK: - Properties

    let note: Note

}

With the above in mind, it isn't surprising that the compiler throws an error. The error is a bit vague, though.

Class ViewController Has No Initializers

The compiler throws an error because the ViewController class doesn't define an initializer that fully initializes a ViewController instance. None of the initializers of the ViewController class assigns a value to the note property. This violates the above requirement.

We know what the error means and what the underlying problem is. How can we resolve the error? There are several options.

Using Optionals

The simplest option is to declare note as a variable of type Note?, an optional. This works fine and it is a pattern I use frequently when I use the Model-View-ViewModel pattern. By declaring note as a variable of type Note?, we can create an instance of the ViewController class and set the note property after initialization.

You may be wondering why this solution doesn't violate the above requirement. Swift automatically assigns nil to any variable property that has an optional type. That is why the error disappears if we define note as an optional.

import UIKit

internal final class ViewController: UIViewController {

    // MARK: - Properties

    var note: Note?

}

This is how we would set the note property of a ViewController instance. First, we initialize a ViewController instance. Second, we assign a Note object to the note property of the ViewController instance.

// Initialize View Controller
let viewController = ViewController()

// Set Note
viewController.note = Note()

This is also known as property injection, a form of dependency injection. We inject the Note object into the ViewController instance. Property injection is a useful and common pattern in software development.

Initializer Injection

There are three downsides to the previous solution. The note property of the ViewController class:

  • has an optional type
  • cannot be declared privately
  • cannot be declared as a constant

We can use initializer injection to resolve these problems. Initializer injection is another form of dependency injection. It may sound complex, but it really isn't. It simply means that we pass the value for the note property to the initializer of the ViewController class. Take a look at this example.

import UIKit

internal final class ViewController: UIViewController {

    // MARK: - Properties

    private let note: Note

    // MARK: - Initialization

    init(note: Note) {
        self.note = note

        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder: NSCoder) {
        fatalError("Use `init(note:)` to initialize a `ViewController` instance.")
    }

}

While this is quite a bit more code, we have addressed the downsides of the previous solution. The note property is a constant, private property and it no longer has an optional type.

Because init(coder:) is a required initializer of the UIViewController class, we also need to implement it in the ViewController class. We have no other option but to throw a fatal error since it should not be used to initialize a ViewController instance. Why is that? The reason simple. It doesn't and cannot accept a Note object, which means we would be back to square one.

This solution seems great, but keep in mind that this only works if you are not using storyboards. I have good news, though. There is a third solution that combines the best of both solutions.

Best of Both Worlds

As of iOS 13 and tvOS 13, it is possible to use initializer injection in combination with storyboards. You can even use this API in combination with segues. The implementation involves two steps.

We first define a designated initializer that accepts an NSCoder instance. The initializer is similar to the one we encountered earlier. Let's update the implementation of the ViewController class.

import UIKit

internal final class ViewController: UIViewController {

    // MARK: - Properties

    private let note: Note

    // MARK: - Initialization

    init?(coder: NSCoder, note: Note) {
        self.note = note

        super.init(coder: coder)
    }

    required init?(coder: NSCoder) {
        fatalError("Use `init(note:)` to initialize a `ViewController` instance.")
    }

}

Notice that the init(coder:note:) initializer defines two parameters, coder of type NSCoder and note of type Note. In the initializer, the vale of the note parameter is assigned to the note property and the init(coder:) initializer of the superclass, UIViewController, is invoked.

With the init(coder:note:) initializer in place, we can put it to use. We load the storyboard and invoke the instantiateViewController(identifier:creator:) instance method. This method accepts the storyboard identifier of the view controller as its first argument and a closure as its second argument. The magic happens in the closure. The closure accepts an NSCoder instance and returns an object of type UIViewController?. The implementation isn't difficult as you can see below.

// Create Note
let note = Note()

// Load Storyboard
let storyboard = UIStoryboard(name: "Main", bundle: .main)

// Initialize View Controller
let viewController = storyboard.instantiateViewController(identifier: "ViewController") { (coder) -> UIViewController? in
    ViewController(coder: coder, note: note)
}

What's Next?

The next time you encounter this error, you should be able to resolve it without a problem. You now understand what the error means and why the compiler throws it.