Create A UIViewController Extended Class With A Custom Initializer - ios

I love Swift, except for this
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
If I create any UIViewController extended class I have to have this ugly, unclear, required init, as the init I don't want.
Is there a way I can set up my code so I can have code setup so I can have my own required init, and then it just calls this at the end? I don't want my client code to have to deal with this, and I'd like to have one clear init() call, instead of a 'initWithSomeCumbersomeName()' call.
If anyone has a clean workaround to this I am all ears!

Related

Swift: 'super.init' isn't called on all paths before returning from initializer?

I am getting this error on the last brace of a init in a class of mine. The class looks something like the following (I market the spot where error happens):
class RecordingViewController: UIViewController, AVCaptureFileOutputRecordingDelegate {
let cameraButton:UIButton?
let camPreview:UIView?
init (cameraButton: UIButton!, camPreview: UIView!) {
self.cameraButton = cameraButton
self.camPreview = camPreview
} //get error here
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
//do a bunch of other stuff
}
I have looked here and here for a solution but both seem like solutions that are either really bad or that are too specific to that question, thus they have not work for me.
I was hoping for a solution to my problem done in such a way that it can help me understand why this error is happening.
Since you inherit from UIViewController, you should call super.init right after you set the variables in your init function
When you inherit a class and implement a new init function or override its own init function you should (almost) always call super.init. Let's take your example, you inherited from UIViewController. UIViewController has a few init functions that you can use to initialize a view controller. if you don't call super.init, all the code inside those functions will not get called and possibly the view controller won't get initialized.
Anyway, this piece of code should work for you:
class ViewController: UIViewController {
var button: UIButton?
init(button: UIButton) {
self.button = button
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Here is what I found on Swift Programming Language:
In the first phase, each stored property is assigned an initial value by the class that introduced it. Once the initial state for every stored property has been determined, the second phase begins, and each class is given the opportunity to customize its stored properties further before the new instance is considered ready for use.
A designated initializer must ensure that all of the properties introduced by its class are initialized before it delegates up to a superclass initializer.
Hope this can explain that question.

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.

Unable to Set Initializer for Custom UICollectionView Cell Class

I am trying to set an initializer for a custom UICollectionViewCell class and I am receiving an error. I am new to Swift and understand that a model class must have a relevant initializer in order to use items from the class. Here is my code and the error I am receiving:
You need to set a default value to your key variable or make it Optional
var key: String?
you need to include the initializer for NSCoder:
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// do something here if you need ...
}
That should fix it for you.
if your 'Key' is not optional then you need to initialize the 'key' in Init().
public required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
// do something here if you need ...
}
The decoder is useful if you want to restore your object to customer object.For example, if you store a class object in core data and read the record from core data then you need to do encode and decode for it.

Why must I keep declaring the same required but not implemented initializer for init(coder aDecoder) for my programatic UIViewController subclass?

Perhaps it is just me, but I find certain aspects of swift... obtuse to say the least.
I don't use Interface Builder most of the time because I like using PureLayout. So I was hoping to make a UIViewController subclass, say PureViewController, that had a handy init without parameters:
class PureViewController : UIViewController {
init() {
super.init(nibName: nil, bundle: nil)
}
}
But this is not okay, for XCode tells me I must also implement init(coder aDecoder: NSCoder). Okay, that's fine! That's why I made this class - so I don't have to do this again for subclasses.
class PureViewController : UIViewController {
init() {
super.init(nibName: nil, bundle: nil)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Ok, now here's what I don't get.
I define a subclass, SomePureViewController : PureViewController, with an initializer init(viewModel:ICrackersViewModel)...
class SomePureViewController : PureViewController {
init(viewModel:ICrackersViewModel) {
super.init()
}
}
But it STILL wants me to define the same stupid initializer till kingdom come!
class SomePureViewController : PureViewController {
init(viewModel:ICrackersViewModel) {
super.init()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Now I understand the idea - there is no init(decoder) in my subclass, even though it is defined in its parent class.
Maybe I've always dealt with this issue with UIViewController and never noticed it before.
My questions are as follows:
Is there something I am doing wrong to cause this behavior?
Is there any way outside of inheritance that I can avoid repeating myself?
Are there plans to any plans to change this?
The point is that one can initialize a possibly derived class just by knowing the base type.
Lets assume a base class
class Base {
let value: Int
required init(value: Int) {
self.value = value
}
}
and a function
func instantiateWith5(cls: Base.Type) -> Base {
return cls.init(value: 5)
}
then we can do
let object = instantiateWith5(Base.self)
Now if someone defines a derived class
class Derived: Base {
let otherValue: Int
init() {
otherValue = 1
super.init(value: 1)
}
required init(value: Int) {
fatalError("init(value:) has not been implemented")
}
}
We are at least able to call
let object2 = instantiateWith5(Derived.self)
violating LSP, but that's a problem of your code, not the language.
Swift has to guarantee that initializers leave your objects in a initialized state, even when they are derived from a base object, so I guess changing that would be a bad thing. If you like to define a UIViewController that is not deserializable and thereby violating LSP it's your decision - but don't expect the language to support you in this.
I think that this is swift issue and there is no way how to avoid this. We all hate this empty - fatal error initializer.
As the initialiser is marked with the required keyword, all subclasses must implement that initialiser and they must also specify required on their implementation so that their subclasses are also required to implement it.
Required Initializers
Write the required modifier before the
definition of a class initializer to indicate that every subclass of
the class must implement that initializer
You must also write the required modifier before every subclass implementation of a required initializer, to indicate that the initializer requirement applies to further subclasses in the chain.”
This has been part of the Swift language since 1.0 and it is unlikely to change.
The issue is actually to do with the use of the required keyword in the UIViewController class definition. In theory this could be changed, but again I think it is unlikely.

Get property from NSCoder in required init(coder aDecoder: NSCoder)

I am implementing a Circle class (subclass of UIView) in Swift that sets its radius in its initializer according to the frame that is passed in init(frame: CGRect) like so:
override init(frame: CGRect)
{
radius = frame.width/2.0
super.init(frame: frame)
}
I also want to ensure for the case when the circle is instantiated from Interface Builder, so I also implement 'required init(coder aDecoder: NSCoder)` (which I am forced to do by Xcode anyway).
How can I retrieve the frame property of the view that is somehow contained in aDecoder. What I want to achieve basically would look like this:
required init(coder aDecoder: NSCoder)
{
var theFrame = aDecoder.someHowRetrieveTheFramePropertyOfTheView // how can I achieve this?
radius = theFrame.width/2.0
super.init(coder: aDecoder)
}
You could compute the radius after the frame has been set by super.init():
required init(coder aDecoder: NSCoder)
{
radius = 0 // Must be initialized before calling super.init()
super.init(coder: aDecoder)
radius = frame.width/2.0
}
Martin's answer is the correct one. (Voted). You might be able to find the way that the base class encodes the frame value and extract it, but that is fragile. (It relies on private details of the implementation of the base class, which might change and break your app in the future.) Don't develop code that depends on non-public implementation details of another class, or of your base class. That's a future bug just waiting to happen.
The pattern in initWithCoder is to first call super to get the values for the ancestor class, then extract the values for your custom class.
When you do it that way, the ancestor class will have already set up your view's frame for you, and you can just use that.

Resources