Required Initializers for a Subclass of Uiviewcontroller

How do I make a custom initializer for a UIViewController subclass in Swift?

class ViewController: UIViewController {

var imageURL: NSURL?

// this is a convenient way to create this view controller without a imageURL
convenience init() {
self.init(imageURL: nil)
}

init(imageURL: NSURL?) {
self.imageURL = imageURL
super.init(nibName: nil, bundle: nil)
}

// if this view controller is loaded from a storyboard, imageURL will be nil

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

Required initializers for a subclass of UIViewController

The problem is: If you declare any stored properties without initial value, you must implement your own initializer to initialize them. see this document.

Like this:

var currentDetailViewController: UIViewController

override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
currentDetailViewController = UIViewController()
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}

convenience override init() {
self.init(nibName: nil, bundle: nil)
}

required init(coder aDecoder: NSCoder) {
currentDetailViewController = UIViewController()
super.init(coder:aDecoder)
}

But, I think this is not what you want.

The correct solution depends on where you initialize currentDetailViewController.

If you always initialize it within viewDidLoad, then you can declare it as an "Implicitly Unwrapped Optional"

var currentDetailViewController: UIViewController!

override viewDidLoad() {
super.viewDidLoad()
self.currentDetailViewController = DetailViewController()
}

otherwise, If currentDetailViewController can be nil, you should declare it as an "Optional"

var currentDetailViewController: UIViewController?

required' initializer 'init(coder:)' must be provided by subclass of UIViewController

If you override any of class's "designated" initializers,Though you don't inherit any other designated initializers. But because UIView adopts the NSCoding protocol,

which requires an init(coder:) initializer. That's why you need to implement init(coder:)

init(coder decoder: NSCoder) {
super.init(coder: decoder)
}

It shows required initializer init must be provided in subclass of UIControl When I override init(frame: CGRect)

Look. According to Apple documentations Swift subclasses do not inherit their superclass initializers by default. They are inherited only in certain circumstances, one of which is: If your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers. So if you're not implementing init(frame: CGRect) all super initializers are inherited.

Also UIView adopts NSCoding protocol, which requires an init(coder:) initializer. So if you're implementing init(frame: CGRect), your class is no longer inheriting super initializers. So you must implement that one too:

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

Swift 2.0 Subclassing a subclass of UIViewController and called convenience initializers

You need to make your initializers designated, not convenience:

class A : UIViewController {
init() {
super.init(nibName:nil, bundle:nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("")
}
}

class B : A {
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("")
}
}

That gives you the inheritance structure you're looking for.

How to subclass a UIViewController and add properties in swift?

At some point the view controller must be initialized by calling init(nibName:bundle:) or init(coder:)

Try this:

class MyViewController: UIViewController {

var prop: Int

init(prop: Int, nibName nibNameOrNil: String? = nil, bundle nibBundleOrNil: Bundle? = nil) {
self.prop = prop
super.init(nibName:nibNameOrNil, bundle: nibBundleOrNil)
}

required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}

}

Swift : Error: 'required' initializer 'init(coder:)' must be provided by subclass of 'UIView'

Note for required: Write the required modifier before the definition of a class initializer to indicate that every subclass of the class must implement that initializer.

Note for override: You always write the override modifier when overriding a superclass designated initializer, even if your subclass’s implementation of the initializer is a convenience initializer.

Above both notes are referred from: Swift Programming Language/Initialization

Therefore, your subclass of UIView should look similar to the sample below:

class MyView: UIView {
...
override init(frame: CGRect) {
super.init(frame: frame)
}

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

Swift: Initialization of UIViewController subclass with custom parameters

You can write class func for your view controller:

class MyViewController {
// ...
class func instantiate(dataSource: MyDataSource, cellAndHeaderManager: MyCellAndHeaderManager) -> MyViewController {
let vc = UIStoryboard(name: "Storyboard", bundle: nil).instantiateViewControllerWithIdentifier("MyViewController") as! MyViewController
vc.dataSource = dataSource
vc.cellAndHeaderManager = cellAndHeaderManager
return vc
}
}

So you can instantiate it with:

let vc = MyViewController.instantiate(dataSource: dataSource, cellAndHeaderManager: cellAndHeaderManager)

You can not instantiate view controller from Storyboard with initializer because there is no suitable initializer in UIViewController.

How to Hide Storyboard and Nib Specific Initializers in UI Subclasses

UI Subclasses & @available(*, unavailable)

*The code below was written and tested in Swift 3

The crux of this solution is creating base subclasses that your custom UI subclasses inherit from. In the examples below, these subclasses are named BaseViewController, BaseView and BaseButton. These subclasses include an initializer that defaults arguments of the superclass's designated initializer that are hidden from their subclasses.

init(coder:) must be implemented in all subclasses since it is a required initializer of the UI superclasses. You can get around this by placing the attribute @available(*, unavailable) above an implementation of that initializer.

The available attribute is used "to indicate the declaration’s lifecycle relative to certain platforms and operating system versions". Using this attribute with the following arguments: @available(*, unavailable) makes the block of code that follows unavailable on all versions of all platforms.
 

UIViewController

class BaseViewController: UIViewController {

// This initializer hides init(nibName:bundle:) from subclasses
init() {
super.init(nibName: nil, bundle: nil)
}

@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
}

class CustomViewController: BaseViewController {
let someProperty: Int

init(someProperty: Int) {
self.someProperty = someProperty
super.init()
}
}

let customViewController = CustomViewController(someProperty: 1)

UIView

class BaseView: UIView {

// This initializer hides init(frame:) from subclasses
init() {
super.init(frame: CGRect.zero)
}

@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
}

class CustomView: BaseView {
let someProperty: Int

init(someProperty: Int) {
self.someProperty = someProperty
super.init()
}
}

let customView = CustomView(someProperty: 1)

UIButton - UIControl Subclass

This UIButton example illustrates how to subclass UIControl subclasses.

internal class BaseButton: UIButton {

// This initializer hides init(frame:) from subclasses
init() {
super.init(frame: CGRect.zero)
}

@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
}

class CustomButton: BaseButton {
let someProperty: Int

init(someProperty: Int) {
self.someProperty = someProperty
super.init()
}
}

let customButton = CustomButton(someProperty: 1)

 

Considerations

I don't advise regularly using @available(*, unavailable) to avoid implementing required initializers. It's beneficial in reducing redundant code that will not be called in this case (since you don't plan to use storyboard/nibs). The appearance of @available(*, unavailable) is reduced by using it in the base classes (and having custom subclasses inherit from the base classes) as opposed to in every custom subclass.

I'm aware that this works in Swift 2 and 3. Its possible that future versions of Swift will not allow this. However, I hope the Swift team comes up with a better way to avoid this redundant code in custom UI subclasses.

Out of curiosity, I tried initiating a BaseViewController subclass from a storyboard. I expected the app to crash with a selector not found error but it called the init?(coder) method even though it was hidden from all platforms. This may be because the available attribute does not hide the init?(coder) initializer from Objective C and that is where storyboard instantiation code runs.

UIKit often makes use use of classes and inheritance whereas the Swift community encourages structs and protocol oriented programming. I include the following headers above my base UI class declarations to discourage base UI classes from becoming a dumping ground for global settings and functionality.

/**
* This base UIViewController subclass removes the requirement to override init(coder:) and hides init(nibName:bundle:) from subclasses.
* It is not intended to create global functionality inherited by all UIViewControllers.
* Alternatively, functionality can be added to UIViewController's via composition and/or protocol oriented programming.
*/

/**
* This base UIView subclass removes the requirement to override init(coder:) and hides init(frame:) from subclasses.
* It is not intended to create global functionality inherited by all UIViews.
* Alternatively, functionality can be added to UIView's via composition and/or protocol oriented programming.
*/

Reference: I found the Initialization section of the Swift Language Guide helpful in understanding the rules for Initializers.

how to init() a swift view controller properly?

Initialization depends on a couple of condition.

  1. If you are using storyboard, you can just remove the init and your VC will have default initializer. Make sure either all of your properties have default value or they are optional.
  2. If you are using xib or just creating view programmatically you can have custom convenience initializer where you pass some extra data this way.
class MyViewController: ViewController {
var answer: Int
convenience init(answer: Int) {
self.init()

self.answer = answer
// Do other setup
}
}


Related Topics



Leave a reply



Submit