Property with '= {return}()' or '{return}'? - swift

There are two ways in Swift to declare complicated property:
option1:
var label: UILabel {
var label = UILabel()
label.font = UIFont(name: "ArialRoundedMTBold", size: 18.0)
return label
}
option2:
var label: UILabel = {
var label = UILabel()
label.font = UIFont(name: "ArialRoundedMTBold", size: 18.0)
return label
}()
What's the difference?

option1 declares a computed property, every time you invoke the property, the result would be re-computed. Computed property is often used to replace computing function. And computed property cannot be declared by let
option2 declares a label and customizes it. It is not a computed property which means it can also be declared as a constant. It can be used as a normal property.

The asnwer provided by #Carrl is good, but I would clarify some things with option2:
What you assign to label in option2, it is actually a closure, and the () means at the and of the curly brackets, that you execute the closure right away.
So imagine:
let labelClosure: () -> UILabel = {
var label = UILabel()
label.font = UIFont(name: "ArialRoundedMTBold", size: 18.0)
println("here2")
return label
}
This is the closure that you assign to label in option2, but this is just a function, without executed (see, there are no () at the end of the curly brackets). So if you want to create a label from that, you should write:
labelClosure()
So wrap things up: in option2 you assign the closures return value to label, in opposition to option1, which is a computed property. And what does a computed property mean? Actually, that doesn't store any value, it just computes and returns its value every time you call it.

Related

Setting a variable to a defined lazy var within a UIView closure cause reference problems

Recently, I was working on a project of mine and I wanted to have multiple labels with the same font, text color, and properties, except their text.
This is the code I wrote:
lazy var profileLabel: UILabel = {
let label = UILabel()
label.font = .displayNameLabel
label.textColor = .profileLabel
label.numberOfLines = .numberOfLines
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
lazy var displayName: UILabel = {
let label = profileLabel
label.text = "Kevin"
return label
}()
lazy var countryLabel: UILabel = {
let label = profileLabel
label.text = "US"
return label
}()
As you can see, to remedy my issue, I created a single label that had all the properties I wanted for all my other labels. For my other labels, I thought I was creating a new label by typing let label = profileLabel. But as it turns out, I wasn't. After consecutive calls of setting the text and adding the labels to my view, only 1 label was actually displayed, and it was the last label added; so in this case, it would be the countryLabel.
It seems to me that in all my calls to let label = profileLabel, I'm just creating a reference to the same profileLabel. And if this is the case, would changing lazy var profileLabel to var profileLabel fix this issue and create a new label with the needed properties every time profileLabel is called?
You were intended to use computed property of swift. But didn’t get it right. Your profile label should have been defined as follows.
var profileLabel: UILabel {
get {
let label = UILabel()
label.font = .displayNameLabel
label.textColor = .profileLabel
label.numberOfLines = .numberOfLines
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}
}

Difference between lazy var and let

The following code works, but when I change lazy var to let, it cannot work. I don't know why
lazy var collectionView : UICollectionView = {
let layout = UICollectionViewLayout()
let cv = UICollectionView(frame: .zero, collectionViewLayout: layout)
cv.translatesAutoresizingMaskIntoConstraints = false
cv.backgroundColor = .red
cv.dataSource = self
cv.delegate = self
return cv
}()
the above code works but the following code has errors:
The reason is you use lazy initialization is when the initial value for a property is not known until after the object is initialized.
Also you do need to declare your lazy property using the var keyword, not the let keyword, because constants must always have a value before initialization completes.
Your data is not yet available, that's why is not working
see more here
From documentation:
“You must always declare a lazy property as a variable (with the var keyword), because its initial value might not be retrieved until after instance initialization completes. Constant properties must always have a value before initialization completes, and therefore cannot be declared as lazy.”
Excerpt From: Apple Inc. “The Swift Programming Language (Swift 3.0.1).”
Check out this book on the iBooks Store: https://itun.es/us/jEUH0.l

What is the difference between the following 3 declarations?

var title: UILabel {
let label = UILabel()
textLabel.font = .systemFontOfSize(13)
return label
}
let title: UILabel = {
let label = UILabel()
textLabel.font = .systemFontOfSize(13)
return label
}()
lazy var title: UILabel = {
let label = UILabel()
textLabel.font = .systemFontOfSize(13)
return label
}()
If I put 'let' in the first one, compiler will complain that 'computed property do not allow let'. Ok, kind of makes sense. The only difference between the first one and second is '=' and '()'. So, does it mean that it is not a computed property anymore?
1.
var title: UILabel {
let label = UILabel()
textLabel.font = .systemFontOfSize(13)
return label
}
It is a read only computed property. Computed properties cannot be let. These are calculated using other stored/computed properties. So they don't have any backing store of their own. Hence, computed properties are always declared as var.
2.
let title: UILabel = {
let label = UILabel()
textLabel.font = .systemFontOfSize(13)
return label
}()
It is a stored property. This is assigned a closure that returns a UILabel object. This closure is executed during the instantiation process of the object and the returned UILabel object is assigned to title.
3.
lazy var title: UILabel = {
let label = UILabel()
textLabel.font = .systemFontOfSize(13)
return label
}()
It is a lazy stored property. It is also assigned a closure that returns a UILabel object. But this closure is not executed during the instantiation process. It is executed whenever this property is first used. After the execution of the closure, the UILabel object returned is assigned to title.
This is computed get-only property, it calculates every time when you try to get it value:
var title: UILabel {
let label = UILabel()
textLabel.font = .systemFontOfSize(13)
return label
}
This is regular property initialized immidiatly by in-place invoked closure (which playing default value role):
let title: UILabel = {
let label = UILabel()
textLabel.font = .systemFontOfSize(13)
return label
}()
This is lazy property, which will be initialized only on first access by in-place invoked closure:
lazy var title: UILabel = {
let label = UILabel()
textLabel.font = .systemFontOfSize(13)
return label
}()
In addition to all #PGDev has said, I would like to point out another way to write your second/third declaration:
Instead of:
let title: UILabel = {
let label = UILabel()
textLabel.font = .systemFontOfSize(13)
return label
}()
you can write:
let title: UILabel = {
$0.font = .systemFontOfSize(13)
return $0
}(UILabel())
It does exactly the same as the above, just the code is written differently :)

Is it possible to perform a deep property comparison dynamically in Swift at runtime?

Let's say we have two instances of UILabel that, to us, are equivalent:
let label1 = UILabel()
label1.text = "Hello world"
let label2 = UILabel()
label2.text = "Hello world"
Let's say the two instances are in an array of type [UIView]:
let views: [UIView] = [label1, label2]
Is there any way to perform an equality check that would find these two instances of UIView to be equivalent without knowing which type they are ahead of time (and therefore which common properties to compare on)?
(Any way to use the fact that these instances are both have a dynamicType of UILabel and dynamically run through the key/value properties of the UILabel class and compare each value that can be compared?)
What's happening:
label1 == label2 // false
views[0] == views[1] // false
What's desired:
areTheSame(label1, label2) // true
areTheSame(views[0], views[1]) // true
We are looking for a way to compare two separate instances, so we can't use ===.
Swift has no reflection so this is not possible. We can't even get a list of attributes.
Also note that for many types there is no definition of equality. Even comparing two floating point values is a problem.
Exactly for this reason we have the Equatable protocol. If you want to compare types, define equality on them. That equality can then go as deep as needed, without the need for any dynamic (unsafe) behavior.
Just to explain another corner case, for example, on UILabel there are some properties that you definitely don't want to compare, namely things like nextResponder or superview. Trying to deep compare those properties would actually end up in a loop. Usually it's not possible to compare two objects for equality without knowing exactly what should and what should not be compared.
Something like this should work:
let views: [UIView] = [label1, label2]
func areTheSameLabel(view1: UIView, _ view2: UIView) -> Bool {
guard let label1 = view1 as? UILabel,
let label2 = view2 as? UILabel else { return false }
return label1.text = label2.text
}
print(areTheSameLabel(label1, label2)) // Should print "true"
print(areTheSameLabel(views[0], views[1])) // Should print "true"
In response to your comments, I think the best avenue is to create a protocol:
protocol ViewEquatable {
var backgroundColor: UIColor? { get set }
var text: String? { get set }
// add any other properties you want to compare here...
}
Then write a function to compare them:
func ==<T: ViewComparable>(lhs: T, rhs: T) -> Bool {
return (lhs.backgroundColor == rhs.backgroundColor) &&
(lhs.text == rhs.text) &&
// whatever other comparison tests you need go here...
}
Something like that is probably your best option, though your requirements are too vague to give a complete answer...

Getting the value from a button

I've got a series of buttons that all contain randomly generated letters (ie. Button one is Z, button 2 is X... and so on). When the user tap on a button, I want to grab the value of that button and create a new label with that value.
This is the code I have now
#IBAction func zeroB(sender : UIButton) {
buttonPress(sender) // highlight the button
var label = UILabel(frame: CGRectMake(0, 0, 250, 50))
label.text = "\(sender.currentTitle))"
label.font = UIFont.systemFontOfSize(26)
letterView.addSubview(label)
}
It creates a label, but the text in the label displays as Optional("Z")).
What am I missing?
I should add that i'm completely new to iOS programming. I'm making an app to teach myself.
Thanks.
Since currentTitle of a UIButton is an optional string, you would be getting the Optional(...) part.
If you know that the title has been set to a non-nil string, use exclamation point to unwrap it:
label.text = "\(sender.currentTitle!))"
This will produce Z) in the label (I am assuming that the extra closing parenthesis is not a typo, and you actually want it in the title of your label). If you do not want the extra parentheses after the title, use
label.text = sender.currentTitle!
current Title is an optional. You can extract the optional in one of two ways:
1)
let labelText = sender.currentTitle!
label.text = labelText
or 2)
if let labelText = sender.currentTitle {
label.text = labelText
}