0

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?

Stormwaker
  • 381
  • 2
  • 12

1 Answers1

2

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.

Sweeper
  • 213,210
  • 22
  • 193
  • 313
  • "it is important that you do your initialiser there properly as well if you are using storyboards" I'm not sure what that means? I am using storyboards. Do I have to initialize elements from storyboard in initializer in code somehow? – Stormwaker May 02 '20 at 14:28
  • @Stormwaker No, I meant that you should implement `init(coder:)` _properly_, also initialising the properties that needs to be initialised, like I did in my answer. Unlike what you did, which is to not initialise anything in `init(coder:)`. I was trying to point out that you should actually focus more on `init(coder:)` because that is the initialiser that is actually called. That aside, does my answer work? – Sweeper May 02 '20 at 14:31