Search code examples
iosnslayoutconstraint

Subclassing NSLayoutConstraint, bizarre behavior of constants


On whatever project you're currently working on, simply

  1. On any screen drop in a UIView

  2. Just add a width constraint (666 or whatever is fine),

  3. Change the custom class of the constraint, to Constrainty

  4. Run the app,

enter image description here

How can this possibly be?

Does it actually call for the value of the constant, "before the class is initialized," or some such?

How can it happen, and how to solve?

At first, I had the two variables as @IBInspectable and I was surprised that didn't work at all. But then I changed them to ordinary let constants - and that doesn't work!

class Constrainty: NSLayoutConstraint {

    let percentageWidth: CGFloat = 77 // nothing up my sleeve
    let limitWidth: CGFloat = 350

    override var constant: CGFloat {

        get { return teste() }
        set { super.constant = newValue }
    }

    func teste()->CGFloat {

        print("\n\n HERE WE GO \(percentageWidth) \(limitWidth) \n\n")

        if let sw = (firstItem as? UIView)?.superview?.bounds.width {

            let w = sw * ( percentageWidth / 100.0 )
            let r = w > limitWidth ? limitWidth : w
            print("result is \(r) \n\n")
            return r
        }

        return 50
    }

}

Solution

  • My wild guess it's all because of a class named UIClassSwapper. It's a private class that handles all the UI objects initialization from the Interface Builder files. I would suggest to replace your let constants with computed properties.

    //...
    var percentageWidht: CGFloat { // nothing up my sleeve
        return 77.0
    }
    
    var limitWidth: CGFloat {
        return 110.0
    }
    //...
    

    UPD

    Swift default property values(properties with values in their declaration) are being set before the initializer call. E.G. if you have a class MyClass with a property let someVar: CGFloat = 12.0 and it's bridged to Objective-C, when you allocate memory for your object and do not call an initializer MyClass *obj = [MyClass alloc] your variable will have a default value of 0.0 and will stay so unless you’ll call an initializer like [obj init]. So my second wild guess is that because NSLayoutConstraint class is written in Objective-C and it's initWithCoder: initializer isn't declared in it's header(it's private), the ObjC-Swift bridging mechanism doesn't recognize it's call as an initializer call(it thinks it is just a simple instance method), so your Swift properties with default values aren't being initialized at all.