Search code examples
iosswiftautolayoutnslayoutconstraintsnapkit

When using Computed variable and Snapkit: No common superview between views


So here's the thing, I'm declaring a property like this:

var aNameLabel: UILabel {
    guard let foo = Applicant.sharedInstance.realName else {
        return UILabel(text: "获取姓名失败", color: .whiteColor())
    }
    return UILabel(text: foo, color: .whiteColor())
}

And when I try to add constraint to the aNameLabel after I did someView.addSubView(aNameLabel), the app would crash every time at this constraint-adding thing, and says No common superview between views

However, when I change the variable into a let constant like this:

let aNameLabel = UILabel(text: "Allen", color: .whiteColor())

The constraint will be added with no complaint. Somebody can help me with this?

UPDATE

With the help of @par , I've changed my code into this:

var aNameLabel: UILabel = {
    guard let foo = Applicant.sharedInstance.realName else {
        return UILabel(text: "BAD", color: .whiteColor())
    }
    return UILabel(text: foo, color: .whiteColor())
}()

And then the aNameLabel would always be assigned with value "BAD", while actually my guard let is successful. How do I fix this?


Solution

  • The problem is that you are creating a new UILabel every time you access the aNameLabel variable (a computed property function runs every time you access it). Presumably you are doing the same thing for the superview of this view (when you access someView in someView.addSubview() in your example above). If so, that's why there's no common superview and you are crashing.

    You should create only one instance of each UIView used by your view controller, so creating a variable as a constant as you've shown is a great approach, or you can use a closure-initializer pattern like so:

    var aNameLabel: UILabel = {
        return UILabel(...)
    }()
    

    Notice in the above example the parentheses after the closing brace. Because it's a closure-initializer it will only be called once just like a let constant.

    Often a UIView created with let isn't appropriate because constant properties need to be initialized before init() returns, and if you're creating views in a view controller you won't have a chance to add views until loadView() is called. In this case, declare your UIViews as implicitly-unwrapped optionals. They will be set to nil by init() which meets the initialization requirement, then you can set them to actual views later when viewDidLoad() is called, for example:

    class MyViewController: UIViewController {
        var someSubview: UIView!
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            someSubview = UIView()
            view.addSubview(someSubview)
            // now set constraints with SnapKit
        }
    }