What's the difference between var label: UILabel! and var label = UILabel( )? - ios

I notice when I'm writing in Swift, if I declare a label in the ViewController as either var label: UILabel! or var label = UILabel(), and then initialize them in the viewDidLoad method, they both seem to, functionally, work identically. So what is the difference between these two?
On a related note, when I declare var label: UILabel! I have to write label = UILabel(frame:CGRectMake(...)), but if I use var label = UILabel() I have to write label.frame = CGRectMake(...). Is this because var label = UILabel() is both declaring and instantiating a class object whereas var label: UILabel! is simply declaring a variable of a type? I'm confused about what's actually going on here.
Thanks in advance!

var label: UILabel!
is declaring that you have a UILabel variable that may be empty (Optional), but you can access it pretending as if it will not - usually that means it would be an IBOutlet that would be set by the interface storyboard on load, before you tried to use the label.
var label = UILabel()
is declaring that you have a label that will always hold a label, and can never be nil. It is ALSO creating an instance of a UILabel when a class instance is created, even though you say you assign a different label to the variable in viewDidLoad()
There's no difference in access, but it does mean no matter when you use that variable it will hold a real value and not be nil.
They both are variables of type UILabel.
I'm not sure why you could not not set the variable to a new UILabel using the CGRect constructor, that should be possible. Perhaps at one point you had said let label = UILabel(), which would mean it was a fixed variable that could not hold a new value - so all you could do was alter the frame of the variable that exists already.
The best approach for something like what you are doing is to declare the variable an Optional:
var label : UILabel?
Which makes it an true Optional, and makes sure that you do not accidentally write code that accesses the variable badly if it's never been set, or sets a value to your first label before you assign the "real" UILabel in your viewDidLoad method. Accessing is a little different, but not much harder - to set text you just use the "?" syntax to optionally call the property if the instance has been set:
label?.text = "fred"

You are correct. UILabel() calls the UILabel no-arg constructor and assigns that object reference to the label variable. So that's a declaration and an initialization.
In the case of the constructor, label is not an optional and is inferred to be the straightforward UILabel type, but in the case of your declaration label is an Optional type. The ! just means it's an optional with a force-unwrapping, so that when you use it as a value, you don't have to type the !; however if you declare it as force-unwrapped and you use it when it's nil-valued, your program crashes.
Note also that you're not allowed to declare:
var label: UILabel
as a class member unless you initialize it in all of your constructors, before the (possibly implicit) call to super.init() because it will complain that there is no initial value, so it might be referenced with no known value -- something the Swift compiler prevents.

Related

Avoiding implicit unwrap variable

I have a question for you guys. I'm trying to avoid do implicit unwrap variables but I haven't figure out how to do it. Here is my example with implicit unwrap
class MyView : UIView {
#IBOutlet var button : UIButton!
var buttonOriginalWidth : CGFloat!
}
There is a way of avoid the implicit unwrap ?
I'll really appreciate your help.
Implicitly unwrapped optionals can be avoided by:
Declare a initial value:
var buttonOriginalWidth: CGFloat = 20.0
Declare its value within init:
var buttonOriginalWidth: CGFloat
init() {
self.buttonOriginalWidth = 20
super.init()
}
You can always declare it as an optional:
var buttonOriginalWidth: CGFloat?
But that of course means you have to unwrap it yourself next time you call it.
You can also use lazy to declare a property that is set after init.
That being said, you should not always avoid using implicitly unwrapped optionals. They are especially useful if you are using Storyboard. #IBOutletand #IBAction are declared mostly as implicitly unwrapped optionals.
#IBOutlet var button: UIButton!
Because if you have configured your Storyboard correctly, it will never be nil.
You can avoid that by adding ? called optional instead of ! which is force unwrapping the variable for example:
#IBOutlet var button : UIButton?
Is a valid point to add it for Outlets connected from storyboard and it actually doesn't prevent you from doing that, but its by default optionals for Outlets connected from storyboard. And also for a variable:
var buttonOriginalWidth : CGFloat?
And to use the a variable which is optional you'll need to check first that its not actually still nil using if let or guard let as the following:
if let widthIsValid = buttonOriginalWidth {
// use the widthIsValid here
}
And if you tried to use the variable without force unwrapping the app at least will not crash for example to do like Which will print it like Optional(478431):
print(buttonOriginalWidth)
I recommend you to read about Optional Chaining in Swift.
In case of the outlet, don't avoid it
#IBOutlet var button : UIButton!
is intended. It reveals a design error (outlet not connected) if the code crashes.
In case of a regular variable – especially scalar types and strings – declare it as non-optional
var buttonOriginalWidth : CGFloat = 0.0
Basically use implicit unwrapped optionals only for outlets and variables which cannot be initialized via init but are supposed to have a value when they are used the first time. It's up to you – the developer – to handle the cases carefully. In all other cases if a variable needs to be optional use a regular optional (?).

UILabel AutoResize Swift

I'm learning Swift, but I Have two "stupid" Problems.
the first, I'd like autosize my UILabel
The second, I have another UIlabel and I want to put name and surname in it's field
I tried with
#IBOutlet weak var title: UILabel!
title.text = currentPerson?.name+""+currentPerson?.surname
But I have this error
Value of optional type 'String?' not unwrapped; did you mean to use
"!" or "?" ?
Generally advised to ask 1 question per post so you get clear responses & don't mix topics, but...
In XCode storyboard "Attributes Inspector" you can change "Autoshrink" from "Fixed Font Size" to minimum font size or scale. Also change "Lines" from default 1 to 0. You'd also need to set some AutoLayout constraints to pin the label to superview or other elements in a way that will allow it to scale. Can't say more w/o seeing storyboard.
By using optional chaining to set the label text you're trying to set the label's .text property to an optional type String? instead of a String. Those aren't equivalent. An optional of type String? might contain a String, or it might be nil. The UILabel expects you to use a String instance, so it's complaining about the mismatch.
One approach is to explicitly check the optional value against nil:
if currentPerson != nil {
title.text = "\(currentPerson.name) \(currentPerson.surname)"
}
else {
title.text = ""
}
Swift's optional binding is similar to the first option, but you create a temporary constant and can reference its properties. If currentPerson is not nil, then the if block executes.
// current convention would be to use "currentPerson" on both sides, which can be confusing. The left side is a temporary constant & the right side is the optional property you've declared somewhere above
if let aPerson = currentPerson {
title.text = "\(aPerson.name) \(aPerson.surname)"
}
else {
title.text = ""
}
Alternatively, as the error message suggests, you could Force Unwrap the optional value to access the name properties:
title.text = currentPerson!.name + " " + currentPerson!.surname
This assumes that currentPerson is never nil. If it is nil, your app will crash here.
Also note you can concatenate using + and " " or with string interpolation.

Weird error in accessing the text of UIButton in swift

When I write a simple function such as this:
#IBAction func buttonTapped(theButton:UIButton) {
println(theButton.titleLabel.text);
}
It gives me an error: UILabel doesn't have a label called text.
However, when I change it to this:
#IBAction func buttonTapped(theButton:UIButton) {
println(theButton.titleLabel?.text);
}
It works fine, but it prints out something like this:
Optional("1");
What I am doing wrong? I am expecting a value of 1. But it is printing out Optional("1") and secondly, it is working fine when println(theButton.titleLabel?.text);
You can get directly from
let title = theButton.currentTitle!
Optional chaining makes the result optional, so you are printing optional value: https://developer.apple.com/library/ios/documentation/swift/conceptual/Swift_Programming_Language/OptionalChaining.html
With optional binding you can print the value only if it exits.
if let text = theButton.titleLabel?.text {
println(text)
} else {
// text doesn't have value
}
#Kirsteins's answer shows how to obtain the button label text in a safe manner.
Remember that:
UIButton has a titleLabel, which is an optional UILabel.
UILabel has a text property, which is an optional String
so there are 2 optionals in the chain. You can use optional binding as in #Kirsteins's answer, or use forced unwrapping:
let text = theButton.titleLabel!.text!
which however I discourage using, because if any of the 2 is nil you'll have a runtime exception. But for completeness it's worth mentioning.
The buttons titleLabel property returns an optional UILabel, that means it's possible that the button doesn't have a titleLabel.
var titleLabel: UILabel? { get }
If you don't set a title to the button, then the button doesn't have a titleLabel property, the iOS framework adds the titleLabel only if the button has a title, I think this happens to reduce memory.
This is why you have to put the "?" (is called optional chaining you can read about it here http://bit.ly/1vrSOi1) in that line, but this usually get auto completed by Xcode itself.
Kirsteins answers it correctly but misses one small detail
if your object can be nil (optional) you need to check first if it exists to then access its value, like this:
if let text = theButton.titleLabel?.text {
println(text)
}
but you can also ignore the if and just call it like this:
let text : String = theButton.titleLabel?.text
// If theButton.titleLabel don't exists then text will be nil
this happen if the IBOutlet was declared with ? but if you declare with ! that means you know that it could be nil, but you never want it to be nil, for a IBOutlet i prefer this approach since if the IBOutlet is not connected then maybe something is worn with my code.
#IBOutlet var theButton : UIButton!
// and get text value as
theButton.titleLabel!.text
this will ensure theButton.titleLabel could be nil, but in this part of code it is required, hope this helps to understand the difference between optional (?) and optional required (!)

Xcode6 autogetters and autosetters

Xcode6 ios swift
I have created my own class and trying to make an autogetter and autosetter, but i don't really know if it's allowed.
var Birthday:NSDate {
get {return birthday}
set(newValue){birthday = newValue}
}
var BirthYear:Int32 {
get {}
set {}
}
The last part of code triggers error, missing return, so my question is that - Is there any possibility to make getter and setter without making a second variable
Stored properties in swift are backed by hidden instance variables - the property itself is its own getter and setter, unless you implement it as a computed property, in that case you have to provide your own getter and/or setter. So when you write:
var birthday: NSDate
you use it as:
let value = classInstance.birthday
to read its value, and
classInstance.birthday = someDate
to assign a new value. You don't have to do anything special to make that work.
Suggested reading: Properties
Side note: by convention variables and property should use lower camel case notation, so they should start with lowercase, and if made up of multiple words, make the first letter of each word in uppercase. For instance:
var single: Int
var multipleWordsVariable: String

Strange behaviour when naming variable in lowerCamelCase

I came across a strange behaviour in Swift while programming a Master-Detail application.
Here's the scenario:
It's a simple Task Manager application. I have two text controls (TaskName, TaskDescription) on the TaskDetailView and two string variables with the same name but in lowerCamelCase (taskName, taskDescription) declared in the TaskDetailViewController.
#IBOutlet var TaskName:UITextField! //UpperCamelCase
#IBOutlet var TaskDescription:UITextView! //UpperCamelCase
var taskName:String? //lowerCamelCase
var taskDescription:String? //lowerCamelCase
I am setting the values of Text controls on ViewDidLoad() as usual:
override func viewDidLoad() {
super.viewDidLoad()
TaskName.text = taskName
TaskDescription.text = taskDescription
}
And I am passing the data in prepareForSegue (from TaskListViewController) as usual:
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if(segue.identifier == "TaskListSegue"){
let detailViewController = segue.destinationViewController as ToDoTaskViewController
let (task, desc) = m_ToDoListManager.GetTask(TaskListView.indexPathForSelectedRow().row)
println("selected \(task) \(desc)")
detailViewController.taskName = task
detailViewController.taskDescription = desc
}
}
The way everything is implemented is correct.
But now when you run the application, the values of text controls are not set.
In fact, the values of the variables also are not set.
What must be happening here?
I have already investigated this problem and also came up with a solution (see my answer below). Please also see Martin R's answer below for a detailed explanation. I just wanted to share this with everyone. I am not sure if anyone has come across this issue.
Update:
Here's the actual code:https://github.com/Abbyjeet/Swift-ToDoList
Here is an explanation:
Your Swift class is (ultimately) a subclass of NSObject.
Therefore the properties are Objective-C properties with getter and setter method.
The name of the setter method for a property is built by capitalizing the first
letter of the property name, e.g. property "foo" has the setter method setFoo:
As a consequence, the setter method for both properties TaskName and taskName is called setTaskName:.
In an Objective-C file, you would get a compiler error
synthesized properties 'taskName' and 'TaskName' both claim setter 'setTaskName:' - use of this setter will cause unexpected behavior
but the Swift compiler does not notice the conflict.
A small demo of the problem:
class MyClass : NSObject {
var prop : String?
var Prop : String?
}
let mc = MyClass()
mc.prop = "foo"
mc.Prop = "bar"
println(mc.prop) // bar
println(mc.Prop) // nil
In your case
TaskName.text = ...
sets the "taskName" property, not the "TaskName". The properties have different type,
so that the behavior is undefined.
Note that the problem does only occur for "Objective-C compatible" properties. If you remove the
NSObject superclass in above example, the output is as expected.
Conclusion: You cannot have two Objective-C properties that differ only in the
case of the first letter. The Swift compiler should fail with an error here (as the
Objective-C compiler does).
The problem you were facing with was not connected to the swift language. Method prepareForSegue is called before loadView. That mean UITextField and UITextView are not initialized yet. That's why fields were not initialized.
You also asked: Why compiler doesn't show any error? That's because any selector performed on nil object doesn't throw an exception. So for example (sorry for obj-c):
UITextField *tf = nil;
[tf setText:#"NewText"];
Will not show any error.
As you said on your own answer to solve your problem you need to add additional fields to your destination controller (copy-paste):
var tAskName:String? //cUstomCamelCase
var tAskDescription:String? //cUstomCamelCase
Why is it happening?
I believe that internally Swift is using lowerCamelCase for text controls names which are not yet initialized and thus failing to set the values. But it is also strange that I didn't get any kind of error.
How did I solve it?
I know that the Swift is case-sensitive. So that was not the issue. So I just changed the case of one letter and named the variables as (tAskName, tAskDescription) and the values were set as expected.
#IBOutlet var TaskName:UITextField! //UpperCamelCase
#IBOutlet var TaskDescription:UITextView! //UpperCamelCase
var tAskName:String? //cUstomCamelCase
var tAskDescription:String? //cUstomCamelCase
So the conclusion is that if I have a control named TaskName, I cannot have a variable named as taskName

Resources