On whatever project you're currently working on, simply
On any screen drop in a UIView
Just add a width constraint (666 or whatever is fine),
Change the custom class of the constraint, to Constrainty
Run the app,
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
}
}
I don't think it's a good idea to subclass NSLayoutConstraint
. I don't think it was designed to be subclassed outside of Apple.
Anyway, the problem is that NSLayoutConstraint
conforms to the NSCoding
protocol, but doesn't declare that it conforms to NSCoding
in the header files. Because of this, Swift doesn't know that NSLayoutConstraint
can be initialized by -[NSLayoutConstraint initWithCoder:]
, so it doesn't generate an override of initWithCoder:
that initializes the instance variables you add in your subclass.
Here's how to fix it.
First, if your project doesn't have a bridging header, add one. The easiest way to add one is to create a new Objective-C class in your project, accept Xcode's offer to create the bridging header, then delete the .h
and .m
files it created for the class (but keep the bridging header).
Then, in the bridging header, declare that NSLayoutConstraint
conforms to NSCoding
:
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
@import UIKit;
@interface NSLayoutConstraint (MyProject) <NSCoding>
@end
Finally, in your Constrainty
class, override init(coder:)
like this:
required init(coder decoder: NSCoder) {
super.init(coder: decoder)!
}
Et voila:
HERE WE GO 77.0 350.0
result is 246.4
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With