0

Pretty much the title; trying to add 2 labels to the stackView while making it (the stack view) a constant results in the error

Instance member 'labelOne' cannot be used on type 'ViewController'

However, making stackView a lazy var makes the issue go away.

Why is that? And is this the only way to resolve this issue; ie, is it possible to let them be constants and not have this issue crop up?

Edit:

Code:

let labelOne: UILabel = {
    let label = UILabel()
    label.translatesAutoresizingMaskIntoConstraints = false
    label.font = UIFont.systemFont(ofSize: 28, weight: .bold)
    label.text = "Label One"
    label.textAlignment = .center
    return label
}()


let stackView: UIStackView = {
        let sv = UIStackView(arrangedSubviews: [labelOne]) //ERROR
        sv.translatesAutoresizingMaskIntoConstraints = false
        sv.axis = .horizontal
        return sv
    }()

1 Answers1

0

The difference has to do with the timing of when those variables' values are set up. Both labelOne and stackView are instance members on your ViewController, and both will get a value that's returned from a closure (from the surrounding { … }() syntax).

However, a "regular" property declared this way will run the closure as soon as your ViewController is initialized, while a lazy property will wait to run the closure until the property is first accessed — well after initialization. The practical difference between these is that self is available to closures run lazily, but self is not available to closures run at initialization time. (The self that you'd use is still being initialized!)

The other gotcha here is that it might not even look like you're accessing self in either closure! However, in Swift, whenever you access a member property, it implicitly uses self — so your reference to labelOne inside the second closure is really to self.labelOne, which is where the requirement that self be fully initialized comes from. Making that closure lazy lets self get ready before it's evaluated, which allows your code to compile.

The usual alternate approach here is to explicitly initialize all your view controller's subviews inside viewDidLoad(), and declare the properties themselves as implicitly unwrapped optionals (IUOs). This would look something like:

class ViewController: UIViewController {
    var labelOne: UILabel!
    var stackView: UIStackView!

    override func viewDidLoad() {
        super.viewDidLoad()
        labelOne = …
        stackView = …
    }
}

This has been called "two-stage initialization," and is implicitly recommended by Apple's View Controller Programming Guide:

Calls the view controller’s viewDidLoad method. Use this method to add or remove views, modify layout constraints, and load data for your views.

Tim
  • 59,527
  • 19
  • 156
  • 165