Can't set #IBInspectable computed property in UIView - swift

I'm trying to add an IBInspectable color to UIView, so that I can set it in the storyboard and later use it in code. In this post regarding UITextField I've seen that I can take advantage of extensions and adding a computed property, but I can't make it work for UIView.
I get a crash: Failed to set (additionalColor1) user defined inspected property on (UIView): [ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key additionalColor1.
Any idea what's causing the crash and how to fix it?
Here's my code:
extension UIView {
#IBInspectable var additionalColor1: UIColor? {
return self.additionalColor1
}
}
For the reference, I'm pasting the code that can be used to set the placeholder color for UITextField (same as the above url). This works ok:
extension UITextField {
#IBInspectable var placeHolderColor: UIColor? {
get {
return self.placeHolderColor
}
set {
self.attributedPlaceholder = NSAttributedString(string: self.placeholder != nil ? self.placeholder! : "", attributes:[NSForegroundColorAttributeName: newValue!])
}
}
}

As mentioned in your question title
Swift extensions can only add computed properties to a type, but they cannot add stored properties.
(For more detailed information please refer to the Extension chapter in The Swift Programming Language.)
The example you posted is actually flawed — even if it has 50 upvotes on Stackoverflow at this time. If you return the value of a property itself from the property's getter you're creating a loop.
#IBInspectable var additionalColor1: UIColor? {
return self.additionalColor1
}
If you have a view and you try to access view.additionalColor1 anywhere in your code your property's getter will be called which returns self.additionalColor1 — or with other words: it returns the property's value again — and guess how? By calling the propery's getter! (And so on...)
The example from the post you mentioned only "works" because the getter is evidently never called. It's only setting that computed property placeHolderColor that changes another stored property, namely the text field's attributedPlaceholder.
So while you can add computed properties to an existing class through an extension you can never think of it as a concrete value that's stored somewhere. Computed properties may only be used to somehow transform the value you assign to it and store the result in existing stored properties.

How is your additionalColor going to be used?
I had to do something similar to this recently, but in my case I was always applying the extra value right away.
For example, I wanted to create a button that looked like a parallelogram. So, I wanted a way to put in a value in the Storyboard, which would apply a CGAffineTransform. I'm not really storing the skew value, just using to change what the thing looks like. Then, in the get, I'm passing back the value from the view's affineTransform routine.
#IBInspectable var skewOffset: Float {
set(newSkewOffset) {
let affineTransform : CGAffineTransform = CGAffineTransform(a: 1.0, b: 0.0, c: CGFloat(newSkewOffset), d: 1.0, tx: 0.0, ty: 0.0)
layer.setAffineTransform(affineTransform)
}
get {
return Float(layer.affineTransform().c)
}
}
So, I'm not storing skewOffset, I'm applying it, and I know how I can look it up later, if I need to get it.

Related

String property returning different values when used directly or in string interpolation

I've just finished debugging a situation where some of my labels were not displaying any text, despite the string in question definitely containing a value (it was printing the line before). I eventually pinned it down to returning different values depending on the way I accessed the string. Please note I'm not looking for ways to work around this, I am looking for a reason I am missing as to why this behaviour happens.
I had a protocol with the following optional String property, set to default to nil:
protocol SomeProtocol {
var titleString: String? { get }
}
extension SomeProtocol {
var titleString: String? {
return nil
}
}
Which was implemented in a class with a not optional String, with title set elsewhere:
class SomeClass: SomeProtocol {
var title: String
var titleString: String {
return title
}
}
When attempting to access the value of titleString from a SomeClass object, it always returned nil, regardless of the title property. This was seen through assigning to a label like:
label.text = someClassInstance.titleString
However, when I printed the value or set the label through
label.text = "\(someClassInstance.titleString)"
everything worked, and it displayed the text.
I've narrowed the source of this down to where I've overridden the optional property with one not optional. Clearly when I access the property directly it is returned by the protocol implementation, while using it by string interpolation returns the class one. What is actually behind this behaviour?
Edit: to demonstrate this behaviour run this gist in an Xcode playground:
https://gist.github.com/CaileanWilkinson/357c17f36d04b522b9bcf1241a825d9f
I feel like this is somewhat similar to the solution of this question of mine. In that question, there is also two almost identical properties - one optional, the other non-optional. And I experienced a similar situation where Swift can't figure out which property I want.
Your titleString in SomeClass is not overriding the titleString property in the protocol. This is reflected in Xcode's suggestions:
You can access both properties like this:
someObject.titleString as String // accesses the one in SomeClass
someObject.titleString as String? // accesses the one in the protocol
My point here is that the type of the expression matters. If the type of expression swift expects is String, then it resolves to the one in SomeClass. If the expected type of the expression is String?, then it evaluates to the one in the protocol.
This explains why setting the label's text without string interpolation will call the property in the protocol (label.text is String?, so it expects a String?) and why using string interpolation will call the property in SomeClass (String interpolation expects a non-optional).

What is the purpose of "self" in Swift [duplicate]

This question already has answers here:
What is "self" used for in Swift?
(10 answers)
Closed 5 years ago.
I read many publications on "self" in Swift and I am starting to get a gist of it, but there is still one thing that is unclear to me.
class Car {
// 1
let make: String
// 2
private(set) var color: String
init() {
make = "Ford"
color = "Black"
}
required init(make: String, color: String) {
self.make = make
self.color = color
}
// 3
func paint(color: String) {
self.color = color
}
}
let car = Car(make: "Tesla", color: "Red")
car.paint("Blue")
I am trying to prove my point with help from the example above.
Several publications that I read indicate that self is used to distinguish 'color' from init() from the 'color' in the parameter from the func paint(color: String).
So when 'self color' is set in the func paint(color: String), which 'color' is it referring to? 'color' from the init() or color from the parameter of func paint(color: String)?
self is a reference to the current instance of the class in which the code is running.
In both the init method and the paint method, it allows you to specify that you wish to set the member variable named color using the value passed in the parameter to the method that is also called color.
The paint method cannot reference the parameter passed to init at all (nor vice versa).
So, in your sample code, both methods set the color of the object to some specified value passed in to the method as a parameter.
The init method sets an initial color for the object.
The paint method then allows you to change the color of the object from that initial color.
This might be clearer if the parameters were simply named differently, e.g.:
required init(initialMake: String, initialColor: String) {
self.make = initialMake
self.color = initialColor
}
func paint(newColor: String) {
self.color = newColor
}
In this case, since the functions are member methods, the self is now entirely optional since the compiler knows that color now can only mean the member called color since there is no other variable or parameter with that name, i.e. the paint method could be written simply as:
func paint(newColor: String) {
color = newColor
}
and this would have the exact same behaviour.
However, some people prefer to keep the self prefix for clarity, even where it isn't strictly required since as well as making the intent clear it can help avoid accidental mistakes if variables or member names are changed.
The self.color in both the init function and the paint function refer to the instance variable on the Car object.
To understand, think of the code without self:
func paint(color: String) {
color = color
}
The function's intent is for the car's color property to be the color passed into it. But with this code, it will not reference the car's color property, and the function wont work as intended. Thus, your function needs a little help in the form of the self keyword.
Also what would be the reason that init() is set up to automatically inititalize the value of "make" and "color" and then require you to initialize on your own after?
You're passing in values with your init function, you're not "automatically initializing the value of "make" and "color". The instance variables you have created for the Car object are non-optional values (Strings), so they cannot be nil, ever. So if you initialize a Car, you have to have their values correctly set after the init function is finished.
self references the object instance. Using self in Swift is optional in most cases.
It's needed in the code you posted because inside the init and the paint methods you have a parameter named color and you also want to access the property named color. Inside the methods, a reference to color will always be to the parameter. So the only way to indicate you want to reference the property named color, you must prefix the reference with self.. If you renamed the parameter so it didn't have the same name as the property, you wouldn't need self.
self.color will always mean a reference to the color property. color will first look for the nearest local variable/parameter of the same name. If found, that's what is used. If not, a property of the same name is used.

Constant property can be modified during initialization? MODIFIED? What?

Quote from the Swift 3.0 office document of the Chapter: Initialization
For class instances, a constant property can be modified during initialization only by the class that introduces it. It cannot be modified by a subclass.
To my understanding the modified involves the action after the definition, aka the action after declaring and assigning value, aka re-assigning values, therefore I tried the following code.
class SurveryQuestion {
let text: String
var response: String?
init(text: String) {
self.text = "do you like music?"
self.text = text //Got an error here
}
func ask(){
print(text)
}
}
And I got an error at line self.text = text. The compiler asked me to change the property textfrom constant to variable. Isn't it says that the constant property can be modified by the initializer of the class which originally introduced it?
Question: Am I understand the word modified wrongly? Is it means the action after the declaring rather than the definition which would lead to the modified is meant to by passing a value to the constant.
I think that the documentation is not clear enough. You can set a constant property only once during initializing. You also would not be able to set it during initialization if the property's value was defined inline. Here is example.
class SomeClass {
let someProperty: String = "A"
init() {
self.someProperty = "" //ERROR: Immutable value "self.someProperty" may only be initialized once.
}
}
The compile time error //ERROR: Immutable value "self.someProperty" may only be initialized once. actually explains it well.

stored property "text" without initial value prevents synthesized initializers

I'm learning swift and have came up with the simple code below.
class ARandom{
var number: Int = 0
var text: String
}
However, Xcode displays the following Error:
stored property "text" without initial value prevents synthesized initializers
Why is this happening? what is an synthesized initialiser? why "text" without initial value prevents systhesised initialiser? Could someone please kindly explain it to me? THanks in advance for any help!
You have a few options here.
Make text optional.
var text: String?
Give text a default value
var text: String = ""
Give text a value in ARandom's initializer
init() { text = "" }
The reason this happens is your are defining text as a String. It is not optional. Essentially you are saying that it always is a String and never nil.
With your current code if you created a new instance of ARandom, text would have no value - and that is not possible if text is not optional
Apple's docs probably explain it a bit better
Classes and structures must set all of their stored properties to an
appropriate initial value by the time an instance of that class or
structure is created. Stored properties cannot be left in an
indeterminate state.
You can set an initial value for a stored property within an
initializer, or by assigning a default property value as part of the
property’s definition.

Can't set nil to an optional property with custom setter in Swift

I have swift class with various properties, some of which have optional types.
class UserObject: PFUser{
//optional property
var optionalPhotoURL:String? {
get {
if let optionalPhotoURL:String = objectForKey("optionalPhotoURL"){
return optionalPhotoURL
}
//not needed but just for emphasis
return nil
}
//I am unable to set UserObject.optionalPhotoURL = nil with this setters
set {
if let newPhotoURL:String = newValue! {
setObject(newPhotoURL, forKey: "optionalPhotoURL")
}else{
self.optionalPhotoURL = nil
}
}
}
}
I am unable to set optionalPhotoURL as nil, if it already had a previous value assigned to it.
I guess my question is, how do i "unset" an optional property with custom setter?
Update
These setters all crash
set {
if let newPhotoURL:String = newValue {
setObject(newPhotoURL, forKey: "optionalPhotoURL")
}else{
self.optionalPhotoURL = nil
}
}
and this
set {
if (newValue != nil) {
setObject(newValue!, forKey: "optionalPhotoURL")
}else{
self.optionalPhotoURL = nil
}
}
What you have here is a computed property.
Swift properties can either be computed or stored. We can observe value changes in our stored properties by using didSet and willSet but here we still have a stored property.
In your case, since you have overridden set and get*, you don't have a stored property, you have a computed property. If you want a computed property to have a backing storage, you must create that independently.
So you may want something like this:
class FooClass {
private var storageProperty: String?
var accessProperty: String? {
get {
return self.storageProperty
}
set {
self.storageProperty = newValue
// or whatever logic you may like here
}
}
}
*: You can't override set without also overriding get. You can however override get without overriding set--this makes a readonly computed value.
Moreover, it's important that we implement our storage properties in this way over relying on key-value coding.
For starters, setObject(forKey:) approach doesn't even work on pure Swift types. This will only work on objects which inherit from Objective-C types. It's an inherited method from NSObject's compliance to NSKeyValueCoding protocol. Why the base object of Objective-C conforms to so many protocols is beyond me... but it does and there's nothing we can do about it.
If we have a code base in which some of our objects are inheriting from Objective-C objects (which basically any project will have, UIViewController, etc), and some of our objects are pure Swift objects (which you will tend to have when you're creating your own Swift classes from scratch), then our Swift objects will not be able to implement this same pattern. If we have some objects of both types, we'll either have to implement the pattern I show above for all of them (and then we have consistency) or we'll have to implement one pattern for some types and another for other types (Swift structs would definitely have to implement the above pattern, you can't just make them inherit from NSObject) (and then we have inconsistency, which we don't like).
But what's far worse about setObject(forKey:) is that the first argument of this method always will be of type AnyObject. There is no type safety to the method at all. Where things are stored via setObject(forKey:) is based purely on the key which we use. When we use setObject(forKey:), we take a pile of type-safety advantages that Swift gives us and we throw them out the window. If we don't care to leverage the advantages Swift gives us over Objective-C, why are we writing it in Swift at all?
We can't even make the stored property private when we use setObject(forKey:). Anyone who knows the key can call setObject(forKey:) and set that object to whatever they want. And that includes objects which are not strings. All we have to do to introduce a crash to this codebase is write a class extension or subclass which has a key collision on a different type other than what you've used (so maybe an NSData value for the same key). And even if it doesn't happen to be a different type, simply duplicating the same key for multiple properties is going to introduce bugs... and bugs that are hard to debug.
Never set a value of a computed property in its set scope by calling itself !
This causes an infinite loop and the app will crash.
I don't know which API setObject:forKey belongs to, but in the case of nil you are supposed to remove the object
set {
if let newPhotoURL = newValue {
setObject(newPhotoURL, forKey: "optionalPhotoURL")
} else {
removeObjectForKey("optionalPhotoURL")
}
}
Your property optionalPhotoURL is a computed property, it does not store any values:
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html
You might want to create an additional property which actually stores the value. However, why do you want to set it to nil, since you are not deleting they object in case of nil.