How to initialize a variable when IBOutlet is initialized?
Asked Answered
F

4

7

It happened for me to write following code snippet in one of my UIViewControllers in my new iOS Swift Application.

var starButtonsCount = starButtons.count
@IBOutlet var starButtons: [UIButton]!

Then straight next to the starButtonsCount variable declaration following error appeared in red.

Error: Cannot use instance member ‘starButtons’ within property initializer; property initializers run before ‘self’ is available.

So I found out that by declaring the starButtonCount variable as lazy we can resolve this error (temporary in the long run of the iOS App development process).

I'm curious to find out what are the other methods to solve this?

Is there a way to trigger the initialization for starButtonCount when the starButtons IBOutlets get initialized?

Faunie answered 16/12, 2018 at 15:53 Comment(5)
It is the purpose of awakeFromNib method.Maricela
I thought so while I was searching on the Internet. But couldn't find a specific answer right to the point! Could you elaborate further in an answer?Faunie
What is the purpose not to declare a constant? The buttons are connected at design/compile time so you are supposed to know the number of buttons.Bartie
@Bartie even though I had declared the starButtonsCount as a constant, still the same error appears. So the focus should be how the BOutlets get initialized and how to catch that and initialize my variable.Faunie
The same error cannot occur if starButtonsCount is declared as let with a constant IntBartie
P
3

awakeFromNib is a method called when every object in a .xib have been deserialized and the graph of all outlets connected. Documentation says:

Declaration

func awakeFromNib()

Discussion

message to each object recreated from a nib archive, but only after all the objects in the archive have been loaded and initialized. When an object receives an awakeFromNib message, it is guaranteed to have all its outlet and action connections already established.The nib-loading infrastructure sends an awakeFromNib

You must call the super implementation of awakeFromNib to give parent classes the opportunity to perform any additional initialization they require. Although the default implementation of this method does nothing, many UIKit classes provide non-empty implementations. You may call the super implementation at any point during your own awakeFromNib method.

As in:

class MyUIViewController : UIViewController {
    var starButtonsCount = 0
    @IBOutlet var starButtons: [UIButton]!
    override func awakeFromNib() {
        super.awakeFromNib()
        starButtonsCount = startButtons.count
    }
}
Pelvic answered 16/12, 2018 at 16:4 Comment(4)
Great, It would be nicer if you could provide the answer with a code example too! :-)Faunie
@RandikaVishman this code can be set in viewDidLoad also so there is no new thing to learn hereAnecdotist
@sh_Khan I think that's bit incorrect since more than viewDidLoad method, awakeFromNib deals directly with connections and initialization made related to the given Nib file (or the StoryBoard in this case). Please read this answer's document excerpt!Faunie
@Sh_Khan Of course there is many places where to put such a code, but OP exactly wanted to know how to capture the fact that outlets have been correctly set, and that is exactly the purpose of awakeFromNib, viewDidLoad is another thing.Maricela
A
3

Another way

var starButtonsCount:Int! 
@IBOutlet var starButtons: [UIButton]! { 
    didSet { 
         starButtonsCount = starButtons.count
    }
}
Anecdotist answered 16/12, 2018 at 15:59 Comment(8)
Works well in that case, but this doesn't let you control multiple/complex settings of IBOutlets...Maricela
@Jean-BaptisteYunès don't get what you mean by multiple complex IBS , but handling in awakeFromNib has same effect as viewDidLoad ? the op is asking for a new way on the fly instead of the usual wayAnecdotist
Personally I don't like the propertyObserver way since it lengthens the number of code lines within a particular UIViewController class and it can be not that much readable too!Faunie
@Sh_Khan Basically you can't control order of outlet initializations, then in case of complex dependancies it is not manageable with didSetMaricela
And why i care with that if eventually the var will be set correctly plus will get an easy observerAnecdotist
@Sh_Khan I did not mean that your answer is wrong, just that it can be problematic in more general case. A better answer would have then been to implement it as a get to have the right value every time...Maricela
can be problematic how ?Anecdotist
@Sh_Khan If a value depend on another, order is not guaranteed!Maricela
P
3

awakeFromNib is a method called when every object in a .xib have been deserialized and the graph of all outlets connected. Documentation says:

Declaration

func awakeFromNib()

Discussion

message to each object recreated from a nib archive, but only after all the objects in the archive have been loaded and initialized. When an object receives an awakeFromNib message, it is guaranteed to have all its outlet and action connections already established.The nib-loading infrastructure sends an awakeFromNib

You must call the super implementation of awakeFromNib to give parent classes the opportunity to perform any additional initialization they require. Although the default implementation of this method does nothing, many UIKit classes provide non-empty implementations. You may call the super implementation at any point during your own awakeFromNib method.

As in:

class MyUIViewController : UIViewController {
    var starButtonsCount = 0
    @IBOutlet var starButtons: [UIButton]!
    override func awakeFromNib() {
        super.awakeFromNib()
        starButtonsCount = startButtons.count
    }
}
Pelvic answered 16/12, 2018 at 16:4 Comment(4)
Great, It would be nicer if you could provide the answer with a code example too! :-)Faunie
@RandikaVishman this code can be set in viewDidLoad also so there is no new thing to learn hereAnecdotist
@sh_Khan I think that's bit incorrect since more than viewDidLoad method, awakeFromNib deals directly with connections and initialization made related to the given Nib file (or the StoryBoard in this case). Please read this answer's document excerpt!Faunie
@Sh_Khan Of course there is many places where to put such a code, but OP exactly wanted to know how to capture the fact that outlets have been correctly set, and that is exactly the purpose of awakeFromNib, viewDidLoad is another thing.Maricela
K
2

Also note there is no need for starButtonsCount to be a stored variable:

var starButtonsCount: Int {
   return starButtons.count
}

In this case it would be probably better to use starButtons.count directly since the variable does not improve the code in no way.

In a general case, there is no need to store derived state unless storing it provides some performance boost (e.g. caching calculations).

Kim answered 16/12, 2018 at 16:16 Comment(0)
B
1

Simple solution is a constant

let starButtonsCount = 8 // or whatever the number of buttons is

IBOutlets are connected at build time so the number of buttons won't change at runtime.

You could add an assert line in viewDidLoad to get informed if the number of buttons changed in a future version of the app

assert(starButtons.count == starButtonsCount, "number of buttons has changed, adjust starButtonsCount") 
Bartie answered 16/12, 2018 at 16:29 Comment(5)
Sorry, the information was insufficient for you it seems! Actually the number of buttons may change over the time if I decide to add more buttons.Faunie
Also, by adding an assert line, isn't it make the program more complex even though it might secure the app from the options to fail? (thinking)Faunie
In this case you get a fatal error in the assert line to change the constant value.Bartie
But thanks for the effort! This is also a simplification for some programs.Faunie
Asserts are only considered in debug scheme. Any runtime check or calculation of a value which will never change is unnecessary.Bartie

© 2022 - 2024 — McMap. All rights reserved.