First of all, what is custom initialization?
Custom initialization can fall into 2 different cases, when creating a new initializer and overriding UIViewController initializer. From my experience, It's rare that I need to override the initializer of UIViewController. I think I only have created new initializers rather than overriding.
- Creating a new designated(?) initializer
class CustomViewController: UIViewController {
let name: String
init(name: String) {
self.name = name
}
}
- Overriding a initializer of super
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
<#code#>
}
As an iOS developer, you might want to use custom initializer in your UIViewController subclass from time to time to control dependency injection. When adding a custom initializer to UIViewController, you need to consider 3 different cases and I will cover all of them.
- Initializing UIViewController programmatically
- Initializing UIViewController using XIB
- Initializing UIViewController from storyboard
For all three cases when you write custom initializers, Xcode will complain you should provide require init?(coder: NSCoder) which is used to create UIViewController from storyboard. The decoder, NSCoder is related to the Interface Builder. Xcode translate everything you set and do in Interface builder to code under the hood. 
You need to implement required initializer regardless of you are using storyboard or not, because it is marked as required by super view.If you are not using storyboards or your view controller has properties to be initialized, you implement below. This won’t be crashing because this will never be called by storyboard.
required init?(coder aDecoder: NSCoder) {
fatalError("We aren't using storyboards")
}
Or you can just reference the fatal error with the super.
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Keeping this required init in mind, what you need to do for each case.
init(name: String) {
self.name = name
}
After initializing custom class you should call designated initializer of super to initilize its own properties. 
You should call the designated initializer for UIViewController, initWithNibName:bundle: 
The final result is
class CodeBasedViewController: UIViewController {
private let name: String
init(name: String) {
self.name = name
super.init(nibName: nil, bundle:nil)
}
required init?(coder: NSCoder) {
fatalError("Never will happen")
}
}
Adding custom initializer to XIB based UIViewController is pretty similar
class XIBBasedViewController: UIViewController {
@IBOutlet weak var titleLabel: UILabel!
private let titleData: String
init(data: String) {
self.titleData = data
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In iOS 12 and earlier, you can't use custom initializers for a UIViewController subclass created from storyboard. In iOS 13 and later, however, Apple provides a way to use custom initializers for a Storyboard based UIViewController with some limitations. -i.e., you can’t use this on relationship segue like navigation controller root view.
https://sarunw.com/posts/better-dependency-injection-for-storyboards-in-ios13/
Convenience initializers are in addition to designated initializers rather than a replacement.
Convenience initializers should end up with calling designated initializers. Make sure your properties have default values or are optionals to call self.init(). 
private var initialData: String = ""
convenience init(initialData: String) {
// To call self.init(), every properties should have default values or be optionals
self.init()
self.initialData = initialData
print("convenience init")
}