Cases I do not have to unwrap a Swift optional? - ios

I'm confused why the label.text = textField.text line works even though textField.text is an optional. Generally I'd write it as:
if let text2 = textField.text {
label.text = text2
}
The above code works fine but why does the label.text = textField.text work fine too? I thought it's a must to unwrap optionals? When must I use the if let syntax and when do I not have to?
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
label.text = ""
}
#IBAction func setTextButtonTapped(_ sender: Any) {
label.text = textField.text
}
#IBAction func clearTextButtonTapped(_ sender: Any) {
textField.text = ""
label.text = ""
}
}

UILabel text is an optional String.
UITextField text is an optional String.
An optional can accept an optional or a non-optional.
A non-optional can only accept a non-optional.
You only need to unwrap an optional when you need a non-optional from it.

As vacawama pointed out:
You can assign an optional to an optional; you can assign a
non-optional to an optional, but you cannot assign an optional to a
non-optional.
So you need to unwrap the optional if you wanted to assign it to a non-optional.
And to unwrap an optional, it's always safer to use if let statement, which extracts the non-optional version of the optional and assign it to a variable for further usage.

It sounds like you're not familiar with implicitly unwrapped optionals.
label is an implicitly unwrapped UILabel, and textField is an implicitly unwrapped UITextField
That's what the ! in the declaration
#IBOutlet weak var label: UILabel!
means.
Thus, it's legal to assign things to it without unwrapping it:
label.text = "some string"
However, that's not necessarily safe. If label is nil, that code will crash.
If label was a normal optional:
#IBOutlet weak var label: UILabel?
you'd have to use code like this:
label?.text = "some string"
Xcode sets up outlets as implicitly unwrapped optionals by default, since if an outlet is not connected, it's good to have your code crash so you know something is wrong.
Another part of this is that it's ok to compare 2 optionals with each other:
If you have
var a: Int?
var b: Int?
It's legal to test a == b. (Put formally, Optionals are Equatable.) That expression will evaluate to true if both are nil, or if both contain the same value. You'll get false if one is nil and the other contains a value, or if both contain different values.
(Note that optionals are not Comparable however. You could not use a < b, for example, since it's not define if nil > 0 or nil < 0.)

Related

Cannot invoke initializer for type 'Double' with an argument list of type '(String?)' - Swift Storyboard

Getting this error trying to convert to a double. Any ideas why?
class ViewController : UIViewController {
#IBOutlet var textField : UITextField!
#IBOutlet var answerButton : UIButton!
#IBOutlet var fahrenheitLabel : UILabel!
#IBAction func tempFieldEditingChange(_ textField: UITextField) {
fahrenheitLabel.text = textField.text
}
#IBAction func showAnswer (_ sender : UIButton) {
let temperatures = ["hot","warm","cool","cold"]
let thresholds : [Double] = [80,60,40,0]
let temperature = Double(textField.text) //<-- (ERROR)
for (i,threshold) in thresholds.enumerated() {
if temperature >= threshold {
fahrenheitLabel.text = temperatures[i]
break
}
}
}
}
Trying to take user input of a temperature and spit out a label with hot warm cool or cold. I know theres something I'm missing please help! Thank you.
textField.text is an optional String, so you need to unwrap it before passing the value to a function that doesn’t accept an optional, such as the Double(_ String:) initialiser.
I would use a guard statement to do so. The string may not be able to be parsed as a Double, so that initialiser also returns an optional, which needs to be unwrapped.
#IBAction func showAnswer (_ sender : UIButton) {
guard let text = textField.text else {
return
}
let temperatures = ["hot","warm","cool","cold"]
let thresholds : [Double] = [80,60,40,0]
if let temperature = Double(text) {
for (i,threshold) in thresholds.enumerated() {
if temperature >= threshold {
fahrenheitLabel.text = temperatures[i]
break
}
}
}
}
}
The UITextField.text property returns an optional String? type. The Double's initializer requires a regular String.
To use the text property, you must first "unwrap" it, i.e. transform it from an optional value into a non-optional. There are several ways to do it:
Forced unwrapping
If you are certain, that the text property is not nil, you may forcibly unwrap it. Be mindful though, as when you try to forcibly unwrap a nil value, your app will crash.
if textField.text != nil {
let temperature = Double(textField.text!)
}
In this case, the text property should never be nil. However, if there were some code changing the property inside the if statement and before the line where the property is forcibly unwrapped, the forced uwrapping might crash.
Optional binding (the preferred way)
This method lets you unwrap the property safely by binding its value to another constant/variable, and once the value is bound, it can be freely used without the possibility of it becoming nil.
if let temperatureValue = textField.text {
let temperature = Double(temperatureValue)
}
The unwrapped temperatureValue constant will remain available and non-optional throughout the whole if-let scope, meaning that up to the closing brace of the if-let statement you can use it freely and will be gone outside the statement's braces. If the textField.text is nil, the inside of the statement's braces will never be executed.
Instead of if-let, you might use the guard-let statement:
guard let temperatureValue = textField.text else {
return
}
let temperature = Double(temperatureValue)
Notice however that any guard statement requires the function to return if the statement fails, but the unwrapped value can be accessed normally in the rest of the function, not only in a closing braces of a statement, like with if-let.
Last thing: the Double's initializer that takes a String also returns an optional value - so in order to use it (e.g. compare to other Double values), you must unwrap it as well:
if let temperature = Double(temperatureValue) {
// compare "temperature" to other Double values
}
This should work with if let way
let atextField = UITextField(frame: .zero)
atextField.text = "55.9"
if let d = Double(atextField.text!) {
print(d)
} else {
print("no")
}
You need to unwrap .text. Here is how I would do it:
class ViewController : UIViewController {
#IBOutlet var textField : UITextField!
#IBOutlet var answerButton : UIButton!
#IBOutlet var fahrenheitLabel : UILabel!
#IBAction func tempFieldEditingChange(_ textField: UITextField) {
fahrenheitLabel.text = textField.text
}
#IBAction func showAnswer (_ sender : UIButton) {
guard let text = textField else {
fatalError("Handle case when textField.text is nil") // TODO
}
guard let temperature = Double(textField) {
fatalError("Handle case when textField.text is nonnil but not a double") // TODO
}
switch temperature {
case ..<40: textField.text = "cold"
case 40..<60: textField.text = "cool"
case 60..<80: textField.text = "warm"
case 80...: textField.text = "hot"
default: fatalError("Non-exhaustive temperature ranges!")
}
}
}

prepare segue unexpectedly found nil while unwrapping an Optional value

Yes, another found nil while unwrapping an Optional value error. I have read tons of other stack overflow posts with similar errors such as this one and many others. I still do not fully understand how to properly deal with unwrapping a variable.
I have a class that is similar to the following:
#IBOutlet weak var nameTextField: UITextField?
#IBOutlet weak var valueInput: UITextField?
var checkbox : CheckBox?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
let name = nameTextField.text ?? ""
let state = buttonState.getIsChecked()
let value : Int? = Int(valueInput.text!)
let isMoveable = true
checkbox = CheckBox(name: name, value: value, state: state, isMoveable: isMoveable)
}
I get the error on the line the "let value : Int? = Int(valueInput.text!) line.
You can safely unwrap the value using if let construct
var value : Int? = nil
if let val = valueInput.text {
value = Int(val) // it would either nil or optional value
}
and also you can do it by nil coalescing operator ?? in a single line
let value : Int? = Int(valueInput.text ?? "")
UPDATE
First check if textfields disconnected from the Interface Builder , if not connect, connect them. and if you become your textfields optionals you also have to safely unwrap the textfields ( you forgot to add it from interface builder and it will not crash if you make them optionals).
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
var name: String? = nil
var value : Int? = nil
let state = buttonState.getIsChecked()
let isMoveable = true
if let nameTextField = self.nameTextField where nameTextField.text != nil {
name = nameTextField.text!
}
if let valueTextField = self.valueInput where valueTextField.text != nil {
value = Int(valueTextField.text!)
}
checkbox = CheckBox(name: name, value: value, state: state, isMoveable: isMoveable)
}
Sahil's answer is on track, but doesn't address that valueInput is also an optional. Use this code:
if let valueInput = valueInput,
let val = valueInput.text {
value = Int(val)
}
In addition regarding properly unwrapping the optional valueInput I wanted to add that chances are that if valueInput is an IBOutlet it's defined as:
#IBOutlet weak var valueInput: UITextField!
That's called an implicitly unwrapped optional. The annoying thing is that since it is also an IBOutlet, if it ever becomes disconnected from the UITextField in Interface Builder, the variable will become nil, and accessing it from anywhere in the code will cause a crash.
Change it to this:
#IBOutlet weak var valueInput: UITextField?
I've also written about this on my blog: http://cleanswifter.com/implicitly-unwrapped-iboutlets/
Note: you didn't show the definition of valueInput so I assumed it to be a UITextField

Implicitly unwrapped optionals

I'm curious why the implicitly unwrapped optional, display.text needs to be unwrapped in the code below. (From Stanford CS193p)
#IBOutlet weak var display: UILabel!
#IBAction func appendDigit(sender: UIButton) {
if let digit = sender.currentTitle {
if userIsTypingNumber {
display.text = display.text! + digit
} else {
display.text = digit
}
}
}
Why is it considered an optional string after the equal sign, but not before?
The UILabel display is an implicitly unwrapped optional, not its property text: the property is a normal optional.
So while you don't have to manually unwrap display, you have to do it for display.text.
Unwrap optional type variable is use to get this value content in variable (it can be nil)
Look at define of UILabel:
public class UILabel : UIView, NSCoding {
public var text: String? // default is nil
...
}
the text property is optional type. So when get (access) this value you need to unwrap it. In your code you use ! to force-unwrap text property from display label.
And when assign value to an optional type you not unwrap, just assign as normal variable.

Unwrapping issue in swift

I am new to swift, so apologies for funny question, but I am quite tanged in this optional type and the unwrapping thing.
So, I am trying to create a calculator for which I have a UITextField to display the digits while pressed or result after calculation and off course some buttons representing digits.
Now I have action methods attached which my digit buttons and return button properly.
I have my UITextField declared like following which is implicitly unwrapped and a mutable string array which is also unwrapped-
#IBOutlet weak var displayTextField: UITextField!
var digitArray : [String]!
The append digit method works fine which just take the digit from the button and displays it in the textfield by appending. But I am getting a Bad Access error in the enter method. I guess, I am trying to add the item in the array improperly. Can anyone please help.
#IBAction func appendDigit(sender: UIButton) {
let digit = sender.currentTitle!
displayTextField.text = digit + displayTextField.text
}
#IBAction func enter(sender: UIButton) {
digitArray.append(displayTextField.text)
}
digitArray is declared but not initialized.
var digitArray = [String]()
The initializer syntax is either a pair of parentheses after the type let x = Type() or in case of an array with the type annotation and a pair of square brackets let x : [Type] = [].
As the compiler infers the type, the declaration [String] is not needed.
Declare variables non optional whenever possible, you will get more and better help by the compiler.
Convert initializer to
var digitArray = [String]()

Can't unwrap 'Optional.None'

When confronting the error fatal error:
Can't unwrap Optional.None
It is not that easy to trace this. What causes this error?
Code:
import UIKit
class WelcomeViewController: UIViewController {
let cornerRad:CGFloat = 10.0
#IBOutlet var label:UILabel
#IBOutlet var lvl1:UIButton
#IBOutlet var lvl2:UIButton
#IBOutlet var lvl3:UIButton
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
override func viewDidLoad() {
super.viewDidLoad()
lvl1.layer.cornerRadius = cornerRad
lvl2.layer.cornerRadius = cornerRad
lvl3.layer.cornerRadius = cornerRad
}
}
You get this error, because you try to access a optional variable, which has no value.
Example:
// This String is an optional, which is created as nil.
var optional: String?
var myString = optional! // Error. Optional.None
optional = "Value"
if optional {
var myString = optional! // Safe to unwrap.
}
You can read more about optionals in the official Swift language guide.
When you see this error this is due to an object such as an unlinked IBOutlet being accessed. The reason it says unwrap is because when accessing an optional object most objects are wrapped in such a way to allow the value of nil and when accessing an optional object you are "unwrapping" the object to access its value an this error denotes there was no value assigned to the variable.
For example in this code
var str:String? = nil
var empty = str!.isEmpty
The str variable is declared and the ? denotes that it can be null hence making it an OPTIONAL variable and as you can see it is set to nil. Then it creates a inferred boolean variable empty and sets it to str!.isEmpty which the ! denotes unwrap and since the value is nil you will see the
Can't unwrap Optional.None error
Fisrt check your storyboard UILabel connection. wheather your UILabel Object or connected or Not. If not it showing Fatal Error.

Resources