Implicitly unwrapped optionals - ios

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.

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!")
}
}
}

Cases I do not have to unwrap a Swift optional?

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.)

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]()

Swift: Comparing Implicitly Unwrapped Optionals results in "unexpectedly found nil while unwrapping an Optional values"

If got this class and would like to compare instances of it based on the value
class LocationOption: NSObject {
var name:String!
var radius:Int!
var value:String!
override func isEqual(object: AnyObject?) -> Bool {
if let otherOption = object as? LocationOption {
return (self.name == otherOption.name) &&
(self.radius == otherOption.radius) &&
(self.value == otherOption.value)
}
return false
}
}
When executing this:
var a = LocationOption()
var b = LocationOption()
a.name = "test"
b.name = "test"
if a == b { // Crashes here!!
print("a and b are the same");
}
This crashes with "unexpectedly found nil while unwrapping an Optional value"? You can copy all this into a Playground to reproduce.
It seems to be due to the Implicitly Unwrapped Optionals. If I declare all fields as Optionals it works as expected.
But in my case, I would like to have these properties as Implicitly Unwrapped Optionals. How should I write the isEqual?
===
UPDATE: #matt is right and as I didn't want to change to "regular" Optionals I ended up with this:
class LocationOption: Equatable {
var name:String!
var requireGps:Bool!
var radius:Int!
var value:String!
}
func ==(lhs: LocationOption, rhs: LocationOption) -> Bool {
let ln:String? = lhs.name as String?, rn = rhs.name as String?
let lr = lhs.radius as Int?, rr = rhs.radius as Int?
return ln == rn && lr == rr
}
You can't unwrap nil. That Is The Law. You can't get around this law merely by using implicitly unwrapped Optionals. Your implicitly unwrapped optionals are still Optionals. So when your test refers to self.radius and self.value and you have not set them, they are nil. So you are trying to unwrap nil and you crash.
In fact, your use if implicitly unwrapped Optionals makes things worse. When you declare them as normal Optionals, they are not implicitly unwrapped and so they are not unwrapped at all — comparing normal Optionals with the == operator is safe, because the compiler inserts a nil test for you. Thus The Law is never violated. But you have thrown away that safety by making these implicitly unwrapped Optionals. That is what the exclamation marks means; it means you can crash. If you don't want that to happen then:
Don't do that. Or:
Insert an explicit test for nil, yourself.

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