Inheriting from UINavigationController in Swift - ios

I have a class that extends UINavigationController.
class MyNavigationController: UINavigationController {
var reservationId: Int
var blankViewController: BlankViewController
init(clientId aClientId: Int, reservationId aReservationId: Int) {
blankViewController = BlankViewController()
clientId = aClientId
reservationId = aReservationId
super.init(rootViewController: blankViewController)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is doing things by the book. My designated initializer is calling super class designated initializer. But the app crashes at run time with this message:
fatal error: use of unimplemented initializer
'init(nibName:bundle:)' for class 'MyiOSApp.MyNavigationController'
Through trial and error I made the problem go away by doing this instead.
init(clientId aClientId: Int, reservationId aReservationId: Int) {
blankViewController = BlankViewController()
clientId = aClientId
reservationId = aReservationId
super.init(nibName: nil, bundle: nil)
}
But init(nibName: nil, bundle: nil) is not a designated initializer of the immediate super class. Technically this should not even compile. So I am super confused. What specific rule was I violating with the first code? And if indeed I was breaking some rule why did the compiler not catch if it?
One potential explanation is that init(nibName:, bundle:) is a required initializer. But that explanation has problems. From what I can see init(nibName:, bundle:), init(rootViewController:) and init?(coder:) are not marked as required in the documentation or the source code view in Xcode. For some reason the compiler thinks that init?(coder:) is required but not so for the other init methods. All of this is very strange.
Edit: many of you are posting answers saying how to get this to work. My question is not about that. I can already get this to work as I have noted as much in my question. My question is about Swift initializer rules, and what I may be violating.

Your subclass will work if you override init(nibName:,bundle:) as well.
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
You will also have to provide default values for your properties.

When you initialize any controller from storyboard it is required to implement
required init?(coder aDecoder: NSCoder) {}
and when you intialize controller manually allocating and intialize, you have to tell name of nib file name along with bundle name. for ref.
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
print("init nibName style")
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
This is good practice to have this init method
convenience init() {
self.init()
}

Related

Is it possible to initialize variable depending on another in class inheriting from UIViewController in Swift?

I'm trying to achieve a seemingly easy thing: initialize constant variable using another one from the same class which is inheriting from UIViewController. I had 2 ideas that worked, but have their problems and don't seem to be the best solution.
Idea 1 - problem: isn't constant
class MyViewController: UIViewController {
let db = Firestore.firestore()
let uid: String = UserDefaults.standard.string(forKey: "uid")!
lazy var userDocRef = db.collection("users").document(uid)
}
Idea 2 - problem: isn't constant and is optional
class MyViewController: UIViewController {
let db = Firestore.firestore()
let uid: String = UserDefaults.standard.string(forKey: "uid")!
var userDocRef: DocumentReference?
override func viewDidLoad() {
super.viewDidLoad()
userDocRef = db.collection("users").document(uid)
}
}
I think it should be possible to achieve that by overriding init(). I've tried couple of implementations I found Googling, but everyone I've tried gave me some kind of error. Few examples:
From this answer.
convenience init() {
self.init(nibName:nil, bundle:nil) // error: Argument passed to call that takes no arguments
userDocRef = db.collection(K.Firestore.usersCollection).document(uid)
}
From this article.
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)! // Property 'self.userDocRef' not initialized at super.init call
}
init() {
super.init(nibName: nil, bundle: nil) // Property 'self.userDocRef' not initialized at super.init call
userDocRef = db.collection(K.Firestore.usersCollection).document(uid)
}
I'm guessing I'm either missing something or those are outdated? I'm surprised such a simple task as overriding initializer is such a bother. What is the proper way to do it?
Your second try is quite close. You've really just missed this rule that you have to follow:
all properties declared in your class must be initialised before calling a super.init.
In your second try, userDocRef is not initialised in init(coder:) and initialised after a super.init call in init().
You should write it like this:
init() {
userDocRef = db.collection(K.Firestore.usersCollection).document(uid)
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
userDocRef = db.collection(K.Firestore.usersCollection).document(uid)
super.init(coder: coder)
}
I don't think you can get rid of the duplicate code here... The best you can do is to create a static helper method that returns db.collection(K.Firestore.usersCollection).document(uid), which means making db static as well, which might be worse now that I think about it.
Note that anything from the storyboards will be created using init(coder:), so it is important that you do your initialiser there properly as well if you are using storyboards.

How to subclass a UIViewController and add properties in swift?

I want to make a new kind of view controller in Swift that has an additional property that must be explicitly initialized. It doesn't make any sense for this property to be nil or have a default value and the controller will only be initialized programmatically. I tried defining it like this:
class MyController : UIViewController {
var prop: Int
init(prop: Int) {
self.prop = prop
super.init()
}
required init(coder aDecoder: NSCoder?) {
fatalError("don't serialize this")
}
}
I tried running this but it crashed because super.init() tries to run the nib constructor which isn't defined, so I tried adding that:
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
But now the compiler complains that prop isn't being initialized. And this is my question: how can I initialize prop correctly here? I don't want a default value, and anything I set will override the correct value that I set in the other initializer.
I kinda hacked around it by setting some default value in the nib init, but then having my first init do this
self.prop = prop
super.init()
self.prop = prop
But other than being really weird and ugly, that makes me worried that now it is possible to initialize my view controller from a nib and end up with the default value, which would be bad.
What is the correct and idiomatic way to do this 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")
}
}
Try the following
class MyController: UIViewController {
var prop: Int
required init?(coder aDecoder: NSCoder) {
fatalError()
}
init(prop: Int) {
self.prop = prop
super.init(nibName: nil, bundle: nil)
}
}

Crazy behaviour with UIViewController inits in Swift

I'm trying to add simple initialiser to a UIViewController in Swift. So far it has been very frustrating...
First I tried adding it as a convenience init:
class ImageViewController: UIViewController {
var model: UIImage
convenience init(model: UIImage){
self.init(nibName: nil, bundle: nil)
self.model = model
}
....
If I do this, the compiler forces me to implement required init(coder aDecoder: NSCoder). I checked the definition of the UIViewController class and there's no such requirement, but anyway.
To make things worse, the compiler complains that the self.init(nibName: nil, bundle: nil) call has an erroneous extra argument in bundle:. Again, I checked the class definition and the initialiser signature requires both parameters.
So I decided to make it a designated init. It's not what I want, as I don't want to lose all the superclass initialisers.
Now it seems to be happy with the self.init(nibName: nil, bundle: nil) call, but it still insists that I implement init(coder aDecoder: NSCoder).
Any ideas of what's going on? I can't make head or tails of this...
The error messages are indeed confusing, but I think they come from the fact that
if the model property has no default value then the required initializers are no longer
inherited from the superclass. With an optional (or implicitly unwrapped optional) property
(which has the default value nil) your code compiles:
class ImageViewController: UIViewController {
var model: UIImage!
convenience init(model: UIImage) {
self.init(nibName: nil, bundle: nil)
self.model = model
}
}
If you don't want the model property to be optional, don't make it optional. Sure, it's a bitch to have to implement initWithCoder:, but it's better to have rock-solid, secure code.
class ImageViewController: UIViewController {
var model: UIImage
init(model: UIImage) {
self.model = model
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This ensures that the only way an instance of ImageViewController can be created is by calling initWithModel: and therefore guarantees that model will always have a nonoptional value.
Maybe in the future, Apple will add a convenient way of doing this, but for now, I must sacrifice convenience for control.

Data passing in Swift UITableViewController Initializer

I am facing a weird problem. I have a Custom class FormDataSource like below
class FormDataSource{
var Sections: Array<SectionDataProvider>?;
var Rows:Array<Array<RowDataProvider>>!;
var formData: Dictionary<String, Any?>!;
init(sections:Array<SectionDataProvider>?, withRows:Array<Array<RowDataProvider>>){
self.Sections = sections;
self.formData = [String:Any?]();
self.Rows = withRows;
for rowArray in self.Rows
{
for row in rowArray
{
self.formData.updateValue(row.actualData, forKey: row.keyForRowData);
}
}
}
}
And a UITableViewController subclass FormViewController like below
class FormViewController:UITableViewController{
var formDataSource:FormDataSource!;
init(with DataSource :FormDataSource) {
self.formDataSource = DataSource;
println("count: \(self.formDataSource.Rows.count)"); //prints 3 correctly
super.init(style: UITableViewStyle.Plain);
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil);
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder);
}
override func viewDidLoad() {
super.viewDidLoad();
println("count: \(self.formDataSource.Rows.count)"); //crash at this line
}
}
when I am creating a new instance of the FormViewController It crashes inside viewDidLoad giving
"fatal error: unexpectedly found nil while unwrapping an Optional value".
The code is given below:
var dataSource = FormDataSource(sections: nil, withRows:rows);
let joinView:FormViewController = FormViewController(with: dataSource);
self.navigationController?.pushViewController(joinView, animated: true);
I guess the cause might be that the variable I am passing is being destroyed. Can anyone please tell me what am I doing wrong?
If you built your TableViewController with InterfaceBuilder then either
init(coder)
or
init(nibName)
is called for initialization.
That means that your formData property is not being initalized and it results in that error.
Set breakpoints in the various init functions to see which one gets called.

"Use of property 'nibName' in base object before super.init initializes it" after updating to Xcode 6.1

I have this code in my iOS app, written in Swift:
class AddHomeViewController: UITableViewController, UITextFieldDelegate
{
...
required override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?)
{
super.init(nibName: nibName, bundle: nibBundle)
}
After updating to Xcode 6.1, I get these errors on the super.init(nibName: nibName, bundle: nibBundle) line. It worked perfectly before:
Use of property 'nibName' in base object before super.init initializes it
Use of property 'nibBundle' in base object before super.init initializes it
Changing the super.init() line to use the OrNil arguments fixes the issue:
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)

Resources