Swift: How to initialise a let constant in a UIViewController subclass - ios

I want the model to be passed in by the implementing developer and I want that to be mandatory.
let model: PagingTutorialModel
init(withModel model: PagingTutorialModel) {
self.model = model
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
Error: Property 'self.model' not initialised at super.init call

View controllers cannot realistically have let values unless you're assign them a default value.
let foo = 3
The problem is, when we initialize view controllers from storyboards, iOS initializes the view controller with init(coder:). We don't have an opportunity to pass values in during initialization, so we cannot have let properties.

The reason is, that in init?(coder:) you also need to set the model. Depending of you setup, one solution could be to trap in init?(coder:):
required init?(coder aDecoder: NSCoder) {
fatalError("Not implemented")
}
But doing so, you can't initialize this view controller from a storyboard. But in my opinion Storyboards are only for prototyping.

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.

IOS Swift confusing error on using nib file for reusable Views

I am trying to do a simple reusable view and for some reason I am getting a un-specific error . I am following this tutorial https://www.youtube.com/watch?v=H-55qZYc9qI . As stated this is my first time trying this . Everything compiles correctly but when I go to that View I get a runtime error . What is weird is that I followed that tutorial exactly and am getting the error. The names are correct and the view is connected properly . Any suggestions
import UIKit
class streamShared: UIView {
#IBOutlet var view: streamShared!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
UINib(nibName: "streamShared", bundle: nil).instantiate(withOwner: self, options: nil)
addSubview(view)
}
}
You've got yourself an infinite recursion: streamShared.init(coder:) is calling itself.
I think the subview should be of type UIView:
import UIKit
// PLEASE name your classes, structs and enums Capitalized!
class StreamShared: UIView {
var view: UIView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.view = UINib(nibName: "streamShared", bundle: nil).instantiate(withOwner: self, options: nil)
addSubview(view)
}
}
Also, change the class of the main view in your xib file to the default UIView (grayed); otherwise, when reading the xib and instantiating the views contained therein, StreamShared.init(coder:) will still be called.

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.

"Ambiguous reference to member 'init(...)" calling baseclass initializer

I have a baseclass:
class ViewController: UIViewController
{
init(nibName nibNameOrNil: String?)
{
super.init(nibName: nibNameOrNil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) { }
}
a subclass:
class OneViewController: ViewController
{
private var one: One
init(one: One)
{
self.one = one
super.init(nibName: "OneNib")
}
required init?(coder aDecoder: NSCoder) { }
}
and a subclass of the above subclass:
class TwoViewController: OneViewController
{
private var two: Two
init(two: Two)
{
self.two = two
super.init(nibName: "TwoNib") <== ERROR
}
required init?(coder aDecoder: NSCoder) { }
}
At the indicated line I get the error:
Ambiguous reference to member 'init(one:)'
I don't understand why the compiler can't figure out I'm referring to ViewController's init(), like it managed to do in One's init().
What am I missing and/or doing wrong?
I don't understand why the compiler can't figure out I'm referring to ViewController's init(), like it managed to do in One's init()
It is because, from inside TwoViewController, the compiler can't see ViewController's init. The rule is:
As soon as you implement a designated initializer, you block inheritance of initializers.
Therefore, OneViewController has only one initializer, namely init(one:). Therefore, that is the only super initializer that TwoViewController can call.
If you want ViewController's init(nibName:) to be present in OneViewController, you must implement it in OneViewController (even if all it does is to call super).
The chapter in the Swift manual on Designated Initializers will clarify it, but basically OneViewController has only one way to set self.one, and that's by initialising using init(one: One). You cannot bypass that, which is what you are trying to do.
If what you were trying to do could succeed, what value would two.one have in the following?
let two = Two(two: SomeTwo)
print(two.one)
Answer - undefined. That's why it isn't allowed.
Having declared One.init(one:) it is now the Designated Initializer, and there's no way to go round it.
you don't necessary pass value into constructor's ViewController . you can define public object in oneViewController and you access of outer .
class OneViewController: ViewController {
public var one: One
}
let vc = storyboard?.instantiateViewControllerWithIdentifier("OneViewController") as OneViewController
let one = One()
vc.one = one

Resources