Why does an SKSpriteNode subclass work without encode(with:) - ios

The NSCoding protocol states:
Any object class that should be codeable must adopt the NSCoding protocol and implement its methods.
The two required methods are init?(coder: NSCoder) and func encode(with: NSCoder).
SKSpriteNode inherits from SKNode which conforms to the protocol. When writing a new SKSpriteNode subclass, Xcode's autocomplete will suggest the following code to satisfy the NSCoding protocol requirement:
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
A call to super also works:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Why does this code satisfy the NSCoding requirements without implementing encode(with: NSCoder)?

Xcode will only tell you to add a required initializer if you write your own designated initialiser in the subclass, but not the encode method, correct?
This is because in fact, the requirements of NSCoding has already been implemented in the superclass, SKSpriteNode. That's why you don't need to implement encode. It has been inherited.
However, initializers are different. You can only inherit initialisers from your superclass under the following rules:
Rule 1
your subclass doesn’t define any designated initializers, it automatically inherits all of its superclass designated initializers.
Rule 2
If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.
Look at rule 1! The designated initialisers will only be inherited if you don't have any designated initialisers yourself! So by adding this:
init() {}
you are adding a designated initialiser to your class, which stops the class from automatically inheriting the initialisers from the superclass. This is why you have to add a required init.

Related

When is "required init?(coder aDecoder: NSCoder)" called on a UIView or UIViewController?

When I create a subclass of UIView or UIViewController with a stored property, Xcode will not compile my project unless I include an implementation of required init?(coder aDecoder: NSCoder). Currently, I have the following implementation to shut the compiler up:
required init?(coder aDecoder: NSCoder) {
fatalError()
}
I understand why I'm required to include this initializer; my subclass needs to conform to the NSCoding protocol because its superclass conforms to it, and this initializer is part of the NSCoding protocol so it needs to work with my class, i.e. initialize all of my class's stored properties (which the superclass version of the initializer won't do).
I imagine that a correct implementation would look something like this:
class MyView: UIView {
let label: UILabel
override init(frame: CGRect) {
label = UILabel()
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
if let label = aDecoder.decodeObject() as? UILabel {
self.label = label
} else {
return nil
}
super.init(coder: aDecoder)
}
override func encode(with aCoder: NSCoder) {
aCoder.encode(label)
super.encode(with: aCoder)
}
}
However, considering that my application has over 50 custom views and view controllers, correctly implementing this function in every custom view and view controller is a lot of work.
So, I'm wondering if it's necessary to implement this initializer correctly, or if I can just leave it throwing a fatal error. In other words, will this initializer ever be called if I don't call it in my own code? I think I read that it might be called by a Storyboard, but my app doesn't use any Storyboards.
This initialiser will be called if an instance of your view is used in a storyboard scene.
It is up to you whether to create a functioning initialiser or not, but it should mostly be a matter of copying code from init(frame:)
It provides an NSCoder instance as a parameter, which you need only if you are using iOS serialization APIs. This is not used often, so you can ignore it. If you are curious to learn, serialisation converts an object in a byte stream that you can save on disk or send over the network.
During the initalization of a view controller, you usually allocate the resources that the view controller will need during its lifetime. So, this include model objects or other auxiliary controllers, like network controllers.

How to Hide Storyboard and Nib Specific Initializers in UI Subclasses

Question
In cases where I know init?(coder:) and other storyboard/nib specific initializers will not be called, can I avoid the requirement to implement or call them in UI subclasses?
Background
Many UIKit classes including UIViewController, UIView and subclasses of UIControl (UIButton, UITextField, etc) adopt the NSCoding protocol. The NSCoding init?(coder:) method is used to instantiate these classes from a storyboard or nib.
NSCoding protocol:
public protocol NSCoding {
func encode(with aCoder: NSCoder)
init?(coder aDecoder: NSCoder)
}
Classes that adopt a protocol with an initializer must implement that initializer as required. Required initializers must be implemented by all subclasses.
I often build iOS apps without storyboards or nibs. I implement UIViewController, UIView and UIControl subclasses completely in code. Yet, I must implement init?(coder:) in subclasses to appease the compiler if I want to provide my own init methods (which I often do). The following examples illustrate this.
The following does not compile
class CustomView: UIView {
init() {
super.init(frame: CGRect.zero)
}
}
// Error:'required' initializer 'init(coder:)' must be provided by subclass of 'UIView'
The following does compile because I provided an implementation of init?(coder:). For code-only UI subclasses, I typically implement 'init(coder:)' by throwing a fatal error to assert that I don't expect it to be called.
class CustomView: UIView {
init() {
super.init(frame: CGRect.zero)
}
required init?(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
}
Subclasses of CustomView also need to implement 'init(coder:)' for reasons stated above.
class SubClassOfCustomView: CustomView {
let someProperty: Int
init(someProperty: Int) {
self.someProperty = someProperty
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
}
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 come I can initialize a UIView without parameters, but its documentation does not have an empty initializer?

let view = UIView()
Why does this compile without an error when the only documented UIView initializer is init(frame: CGRect)?
Specifically, I am trying to write a new class that inherits from UIView, but this code throws an error:
class SquadHorizontalScrollViewCell: UIView {
init(var: FIRDataSnapshot){
super.init()
....
It says it must call a designated initializer.
UIView inherits from UIResponder which inherits from NSObject.
The NSObject.init() initializer is accessible on UIView, but not on subclasses of NSObject which replace this designated initializer.
Let's consider an example.
class A: NSObject {
init(_ string: String) { }
}
This leads to a compiler error for let a = A() - missing argument #1 for initializer because this initializer replaces the designated init() initializer for NSObject in the subclass.
You just need to define the initializer of the subclass as a convenience initializer:
class A: NSObject {
convenience init(_ string: String) {
self.init() // Convenience initializers must call a designated initializer.
}
}
let a = A() now compiles.
UIView can also compile with other designated initializers defined in the subclass, since its designated initializer is not known at compile time. As per the docs, if instantiating programmatically, init(frame:) is the designated initializer, otherwise init() is the designated initializer. This means that UIView inherits the NSObject designated initializer rather than replacing it as in the above example.
In your example:
class SquadHorizontalScrollViewCell: UIView {
init(var: FIRDataSnapshot){
super.init()
We see that the designated initializer is init(frame: CGRect), so you have to call this designated initializer instead.
A designated initializer should call its superclass designated initializer.
In this case super.init() is the designated initializer of NSObject not UIView.
It would be UIView's responsibility to call UIResponder init ,I guess it has no designated initializer, hence UIView will call Super.init in its init(frame:CGrect) initializer. check "Initializer Delegation"
for why let x = UIView() is ok , its because of this
Unlike subclasses in Objective-C, Swift subclasses do not inherit
their superclass initializers by default. Swift’s approach prevents a
situation in which a simple initializer from a superclass is inherited
by a more specialized subclass and is used to create a new instance of
the subclass that is not fully or correctly initialized. (Apple)
since UIView is objective c class it still can do it. but you won't be able to call SquadHorizontalScrollViewCell() unless you did not provide any initializer or you overrides the designated initializer of the superclass (UIView)
Check this link for more info
For UIView init(frame: CGRect) is default initializer. You must call it after initialize your instance variable. If you take view from NIB then init?(coder aDecoder: NSCoder) is called instead of init(frame: CGRect). So in that case you have to initialize your instance variable in awakeFromNib() method. In this case your code should be like this:
class SquadHorizontalScrollViewCell: UIView {
init(firDataSnapshot: FIRDataSnapshot){
// intialize your instance variable
super.init(frame: CGRectZero) // set your expected frame. For demo I have set `CGRectZero`
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
For more info you can check this link https://developer.apple.com/reference/uikit/uiview
At a certain point your view will need to be init with something, that is why the compilation is complaining, because it cannot find how to start the initialisation of your custom view. Because at the end, a view will be init from a xib (init(coder aDecoder: NSCoder)), or from a frame ( init(frame: CGFrame)). So here, the easiest way is to call super.init(frame: CGRectZero) at least in your custom init method.
init (var: FIRDataSnapshot) {
super.init(frame: CGRectZero)
}
// This method below is always needed when you want to override your init method
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
but you'll still need to set the size of your frame etc.
You'll notice if you create your own UIView subclass and only override init(frame:) with a log statement, then instantiate this new class using just init(), your init(frame:) is actually called with a zero-sized frame. So the designated initializer is still getting called.

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

This code works:
import UIKit
class wheel: UIControl {
}
But this code doesn't:
class wheel: UIControl {
override init(frame: CGRect) {
super.init(frame: frame)
}
It shows error "required initializer init must be provided in subclass of UIControl" when I override init(frame: CGRect) but not init(coder aDecoder: NSCoder).
Why do I have to implement init(coder aDecoder: NSCoder)? And why don't I need to implement it if I didn't implement init(frame: CGRect)?
I found a similar Stack Overflow post but it didn't explain:
Swift : Error: 'required' initializer 'init(coder:)' must be provided by subclass of 'UIView'
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)
}
When creating your model Object subclasses, you may sometimes want to add your own custom initialization methods for added convenience.
Due to some present limitations with Swift introspection, these methods cannot be designated initializers for the class. Instead, they need to be marked as convenience initializers using the Swift keyword of the same name like this

Create convenience Initializer on subclass that calls failable Initializer

Tried many variations of the derivation below but none work.
import Foundation
import SceneKit
class test:SCNScene{
override init(){
super.init()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
convenience init? (i:UIViewController){
self.init(named:"") //Compile Error:use of self delegating initializer before self.init is called
}
}
According to Swift documentation, rule 2 of initialization, shouldn't init?(named:String) convenience failable Initializer be available after implement the 2 designated Initializer? What am i getting wrong?
The initialiser delegation rule #2 states
A convenience initializer must call another initializer from the same
class.
Your class doesn't define init?(named:String), so it will call superclass initialiser (which you do have access to under the other rule #2 you were referring to), but this won't satisfy the requirement to call a non-convenience initialiser from your class.
You can simply call self.init before calling the superclass initialiser.
convenience init? (i:UIViewController){
self.init()
self.init(named:"")
}

Resources