NSCoding : How to create required init in inherited classes
Here is a simple example:
class Source: NSObject, NSCoding {
var name: String?
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(name, forKey: "name")
}
required init?(coder aDecoder: NSCoder) {
name = aDecoder.decodeObjectForKey("name") as? String
}
}
class RSSSource: Source {
var rssName: String?
override func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(name, forKey: "rssName")
super.encodeWithCoder(aCoder)
}
required init?(coder aDecoder: NSCoder) {
rssName = aDecoder.decodeObjectForKey("rssName") as? String
super.init(coder: aDecoder)
}
}
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)
}
Class does not implement its superclass's required members
From an Apple employee on the Developer Forums:
"A way to declare to the compiler and the built program that you really
don't want to be NSCoding-compatible is to do something like this:"
required init(coder: NSCoder) {
fatalError("NSCoding not supported")
}
If you know you don't want to be NSCoding compliant, this is an option. I've taken this approach with a lot of my SpriteKit code, as I know I won't be loading it from a storyboard.
Another option you can take which works rather well is to implement the method as a convenience init, like so:
convenience required init(coder: NSCoder) {
self.init(stringParam: "", intParam: 5)
}
Note the call to an initializer in self
. This allows you to only have to use dummy values for the parameters, as opposed to all non-optional properties, while avoiding throwing a fatal error.
The third option of course is to implement the method while calling super, and initialize all of your non-optional properties. You should take this approach if the object is a view being loaded from a storyboard:
required init(coder aDecoder: NSCoder!) {
foo = "some string"
bar = 9001
super.init(coder: aDecoder)
}
Swift: Implementing Protocol Initializer in a Class
Surely the designated superclass initializer would be inherited?
No, not always. If the subclass defines its own designated initialisers, then it won't automatically inherit the superclass' designated initialisers. Consider the following example:
class Foo {
init() {}
}
class Bar : Foo {
var str: String
init(str: String) {
self.str = str
}
}
let b = Bar() // illegal – what value would the 'str' property have?
As Bar
defines its own init(str:)
designated initialiser, it doesn't automatically inherit Foo
's designated initialiser init()
. This prevents unsafe initialisation in cases where the subclass declares its own stored properties.
Marking init()
as required
enforces Bar
has an init()
, be it through providing its own implementation:
class Foo {
required init() {}
}
class Bar : Foo {
var str: String
init(str: String) {
self.str = str
}
// implement required init(), as Bar defines its own designated initialiser.
required init() {
self.str = "foo" // now str is correctly initialised when calling init()
}
}
let b = Bar() // now legal
Or by inheriting Foo
's implementation (when Bar
doesn't define its own designated initialisers):
class Foo {
required init() {}
}
class Bar : Foo {
// inherits init() from Foo, as Bar doesn't define its own designed initialisers.
}
let b = Bar() // legal
Correctly init NSCoder in sub class when init NSCoder is convenience method in base class in Swift
Because the rules state that
- A designated initializer must call a designated initializer from its immediate superclass.
- A convenience initializer must call another initializer from the same class.
- A convenience initializer must ultimately call a designated initializer.
Swift, Trouble Instantiating NSCoding Compliant Class
That's really simple - you need to add another designated initializer, typically init(frame:)
for UIView
. You can actually just call super.init(...)
in it.
The problem is that once you add one initializer, the other initializers stop being inherited from superclass and you have to define them.
Inherited convenience initializer - why does this work?
A sub class has the initialisers defined in it and the initialisers from the super class (unless of course any of them have are override in the sub class). So in this case there are 3 init for RecipeIngredient
From RecipeIngredient
init(name: String, quantity: Int){
self.quantity = quantity
super.init(name: name)
}
override convenience init(name: String){
self.init(name: name, quantity: 1)
}
and one from Food
convenience init() {
self.init(name: "[unnamed]")
}
Now a convenience init must call a default init, either directly or via another a convenience init so the chain of calls here becomes
Food.init() -> RecipeIngredient.init(name:) -> RecipeIngredient(name:quantity) -> Food.init(name:)
This can be easily seen by adding print(#function)
to each init which gives the output for
let mystery = RecipeIngredient()
init()
init(name:)
init(name:quantity:)
init(name:)
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.
Related Topics
Raw Depth Map Sdk for iPhone X
How to Select Table View Row Selection with Custom Checkbox Button
Auto Layout How to Hide 1 View in a View with 3 Equal Width Views
Value of Type 'string' Has No Member 'stringbytrimmingcharactersinset'
Parse Cloud - Livequeries - iOS Client Doesn't Work
Using Delegates to Transfer Data from One Tableview to Another
Cfbundledocumenttype Is Not Working in Myproject-Info.Plist File
Conversion from String to Date in Swift Returns Nil
Load Custom Error HTMLstring When Wkwebview Loadrequest Fails
iOS Lazy Var Uibarbuttonitem Target Issue
How to Use Sketch UI Elements Directly into Xcode
Uitabbar Change Background Color of One UItabbaritem on iOS7
How to Change Tabbar Icon Color in iOS
Dynamic Cell Height with Sdwebimage