Swift
iOS Development
ViewController
Initializers
Customization

Swift Custom ViewController initializers

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

Introduction

Custom initializers are a good way to enforce that a view controller always receives the dependencies it needs. The correct initializer pattern depends on how the view controller is created: purely in code, from a nib, or from a storyboard.

Programmatic View Controllers

If the view controller is created entirely in code, a custom initializer is straightforward. Initialize your stored properties first, then call the designated superclass initializer.

swift
1import UIKit
2
3final class ProfileViewController: UIViewController {
4    private let userID: String
5
6    init(userID: String) {
7        self.userID = userID
8        super.init(nibName: nil, bundle: nil)
9    }
10
11    @available(*, unavailable)
12    required init?(coder: NSCoder) {
13        fatalError("Use init(userID:) instead")
14    }
15
16    override func viewDidLoad() {
17        super.viewDidLoad()
18        view.backgroundColor = .systemBackground
19        title = userID
20    }
21}

This pattern is excellent for dependency injection because callers cannot create the controller without providing the required input.

Storyboards Need coder-Based Initialization

Storyboards instantiate view controllers through init(coder:), so a plain init(userID:) will not be called automatically. For storyboard-backed controllers, the modern approach is to add a custom coder initializer and use the storyboard creator closure.

swift
1import UIKit
2
3final class ProfileViewController: UIViewController {
4    private let userID: String
5
6    init?(coder: NSCoder, userID: String) {
7        self.userID = userID
8        super.init(coder: coder)
9    }
10
11    required init?(coder: NSCoder) {
12        fatalError("Use storyboard creator with userID")
13    }
14}

Then instantiate it like this:

swift
1let storyboard = UIStoryboard(name: "Main", bundle: nil)
2let vc = storyboard.instantiateViewController(identifier: "ProfileViewController") { coder in
3    ProfileViewController(coder: coder, userID: "user-123")
4}

That gives you storyboard layout plus real dependency injection, instead of setting required data after initialization and hoping nothing touches it too early.

Keep Initialization Focused

Initializers should capture required state, not perform view work. Avoid touching outlets, view geometry, or presentation logic in an initializer, because the view hierarchy is not loaded yet.

Good things to do in an initializer:

  • store dependencies
  • choose configuration flags
  • set simple non-view state

Better left for later lifecycle methods:

  • outlet access
  • network requests tied to visible UI
  • layout work
  • navigation side effects

That separation keeps the controller easier to test and reason about.

Use Factories When Construction Gets Busy

If the initializer starts collecting several services, IDs, and feature flags, move the assembly into a factory instead of spreading it across the app.

swift
1struct ProfileSceneFactory {
2    let apiClient: APIClient
3
4    func makeProfileViewController(userID: String) -> UIViewController {
5        ProfileViewController(userID: userID, apiClient: apiClient)
6    }
7}
8
9final class ProfileViewController: UIViewController {
10    private let userID: String
11    private let apiClient: APIClient
12
13    init(userID: String, apiClient: APIClient) {
14        self.userID = userID
15        self.apiClient = apiClient
16        super.init(nibName: nil, bundle: nil)
17    }
18
19    @available(*, unavailable)
20    required init?(coder: NSCoder) {
21        fatalError("Use init(userID:apiClient:) instead")
22    }
23}
24
25final class APIClient {}

This pattern helps when multiple coordinators or routers need to create the same screen consistently.

Storyboard Segues Need Extra Care

If navigation happens through storyboard segues, you do not control initialization directly in the same way. In that case, the traditional fallback is property injection during prepare(for:sender:). It works, but it is weaker than initializer injection because the destination can briefly exist in an incomplete state.

If the dependency is truly required, consider switching that screen to:

  • explicit programmatic creation
  • a storyboard creator closure
  • a factory that owns scene assembly

That usually produces stronger invariants.

Common Pitfalls

The biggest pitfall is defining a custom initializer on a storyboard-backed view controller and assuming Interface Builder will use it. It will not unless you instantiate with the creator closure that matches the coder-based initializer.

Another common mistake is reading outlets or touching view during initialization. The view may not be loaded yet, so that work belongs later in the lifecycle.

Teams also overuse optional properties for required dependencies. If a value is required, make it a let and force callers to provide it during construction.

Finally, using fatalError in required init?(coder:) is reasonable only when that construction path truly must never be used.

Summary

  • Use custom initializers to enforce required dependencies.
  • For code-created controllers, initialize stored properties and call super.init.
  • For storyboard controllers, use a coder-based initializer plus the creator closure.
  • Keep initializers focused on state, not on view work.
  • Prefer initializer injection over optional property injection when the dependency is required.

Related reading
Free course
Beginner
7 lessons
2 hours
Tackling System Design Interview Problems

A short course that equips you with the skills to approach system design interviews methodically.

Start the free course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

Interview Questions practice on Codemia

Over 8,000 real interview questions from top companies, searchable by company and role.

Browse interview questions

All Rights Reserved.