Optional Chaining in one step? - swift

Why does this work (Example 1):
if let numString:String = Model.selectedLocation?.zip{
let callString:String = String(format:"tel:%#",numString)
//more code here
}
But not this (Example 2):
if let numString:String = String(format:"tel:%#",Model.selectedLocation?.zip){
//more code here
}
In the second example, Xcode throws an error and wants zip to be unwrapped like:
String(format:"tel:%#",(Model.selectedLocation?.zip)!)
but if I do that the app will crash when zip is nil.
QUESTION:
Is there a way to make the second example above work or is it not possible/correct?

Avoid redundant type annotations
Avoid String(format:) unless you need it. It's a method of NSString from the Foundation framework, which has several consequences:
It requires Foundation to be imported.
It implicitly bridges your String to NSString.
It won't work in Swift 3, because bridging was made explicit.
The root issue here is that String(format:) returns String? (since the format string could be invalid). You can avoid this entirely by using Swift's string interpolation:
if let numString = Model.selectedLocation?.zip {
let callString = "tel: \(numString)"
//more code here
}
...or simple concatination:
if let numString = Model.selectedLocation?.zip {
let callString = "tel: " + numString
//more code here
}

Strictly spoken Example 2 is neither optional binding nor optional chaining because String(format...) returns a non-optional String and the format parameter must be non-optional, too.
Example 1 is the correct and recommended syntax to handle the optionals.
Edit: I totally agree with Alexander's answer (except that String(format:) returns String?)

UPDATED
It is because in String(format: " ", ), the arguments must be not-nil, hence the !.
when using if-let check for optionals, statements must return optionals
// Assuming Model.selectionLocation.zip is of String type
if let numberString = Model.selectedLocation?.zip {
let formattedString = String(format:"tel:%#", numberString)
}
or use guard
guard let numberString = Model.selectedLocation?.zip else {
return
}
let numberString = String(format:"tel:%#", numberString)

Related

Swift Optionals: When should you be expecting them

So I am teaching myself Swift and I get optionals when I am declaring them, so for example:
var thisString:String?
I would have to either force unwrap thisString, or use
if let anotherString = thisString {
or use
guard if let another string = thisString else { return }
or nil coalesce it
let anotherString = thisString ?? "Something else"
But where I am getting hung up is there are times I create something that I don't think it an optional but the compiler does.
For example, why is the URL an optional here?
let myURL = URL(string: "https://itunes.apple.com/search?term=U2")
var myRequest = URLRequest(url: myURL!)
I didn't declare it as an optional and it clearly has a value. So why does the compiler see this as an optional? If I didn't force unwrap it I would get this error:
Value of optional type 'URL?' not unwrapped; did you mean to use '!' or '?'?
Is there some standard that I am missing? I've gone over both the docs and Swift Programming Book from Big Nerd and I still don't seem to get this part.
Thanks for the assist.
But where I am getting hung up is there are times I create something that I don't think it an optional but the compiler does.
But what you think is not what matters. What matters is what the API call you are making actually returns. You find that out by reading the docs.
So why does the compiler see this as an optional
Well, let's read the docs. You are calling URL(string:). That is the same as URL's init(string:). Look at the docs:
https://developer.apple.com/documentation/foundation/url/1779737-init
The declaration is
init?(string: String)
See the question mark? That means, "This call returns an Optional." So you should expect an Optional here.
The compiler can't determine if the url that you define in the string is valid or not.
Suppose instead of:
let myURL = URL(string: "https://itunes.apple.com/search?term=U2")
You miss typed the myURL definition as:
let myURL = URL(string: "https:/itunes.apple.com/search?term=U2")
The string contains a malformed URL, so the program would crash the moment you went to define myURL.
let myURL = URL(string: "https://itunes.apple.com/search?term=U2")
Here you are creating an url from string.That string might not be a valid string.All the strings are not valid url. So you are getting an optional because if that string can be turned in to a valid url then that url will be returned else nil will be returned. See the Apple documentation here.The initializer what you are using is a failable initializer itself.
init?(string: String)
#Keshav is correct, to get a better idea, hold the option button and click on the 'string' part of the init function for the URL class. You will see in the swift reference the init declaration is init?(string: String). This means that a optional is returned. Any function can return a optional, you can have func returnMyString(_ myString: String) -> String {} or func returnMyString(_ myString: String) -> String? {}. Both of those functions are pretty much the same, except the second one returns a optional.
URL has optional initializers. For example you can have
class A {
init?() {
return nil //You will get Optional<A>.none
}
}
A() === Optional<A>.none //true
which kind of implies that initialization failed. Such initializers wrap returned object into Optional. In Swift nil == Optional<Any>.none so you can speak of them interchangeably.
For example if you will attempt to construct a URL with something that is not an actual url, it will return nil.
let notAURL = URL(string: "{") //notAURL will be nil
On a side note: I believe optional initializers are a very poor design choice since they don't communicate anything about what went wrong inside the init. If init is fallible, it should throw. I don't understand why Swift designers allow optional initializers and I see why it births a lot of confusion.

Why is this still optional?

I make a query that returns a NSNumber. I then attempt to cast the NSNumber to String. For some reason, it always prints/ compares as an optional...but when I check the variables type it says string...Why is it optional? I need it to be a string!
let whoseTurn = selectedGame?["currentSender"]
let whoseTurnAsString: String = String(describing: whoseTurn)
if let whoseTurn = selectedGame?["currentSender"] as? NSNumber {
let whoseTurnAsString = "\(whoseTurn)"
print(whoseTurnAsString)
}
This is the right way to do optional chaining and make sure you are not forcing an optional
whoseTurn is an optional wrapping your NSNumber. You aren't "casting" it here to a string, you are making a string that "describes" it, and in this case that description includes the fact that whoseTurn is an optional... If you don't want that you'll need to unwrap it,
let whoseTurnAsString: String = String(describing: whoseTurn!)
(note the ! at the end)
This line of code let whoseTurn = selectedGame?["currentSender"] will return an optional.
This line of code let whoseTurnAsString: String = String(describing: whoseTurn) will return a String describing that optional value, which will be a string like this: Optional(5) or Optional(6). It describes the value saying that it is an optional.
So you need to unwrap that optional in order to get the wrapped value, you can force unwrap selectedGame like this:
let whoseTurn = selectedGame!["currentSender"] and then use the normal String initializer like this: String(whoseTurn).
Or, preferably, safely unwrap it like this:
if let whoseTurn = selectedGame?["currentSender"] {
let whoseTurnAsString = String(whoseTurn)
}
String can be optional also '?' or '!' indicates optional, check documentation on optionals.

Convert optional string to int in Swift

I am having troubles while converting optional string to int.
println("str_VAR = \(str_VAR)")
println(str_VAR.toInt())
Result is
str_VAR = Optional(100)
nil
And i want it to be
str_VAR = Optional(100)
100
At the time of writing, the other answers on this page used old Swift syntax. This is an update.
Convert Optional String to Int: String? -> Int
let optionalString: String? = "100"
if let string = optionalString, let myInt = Int(string) {
print("Int : \(myInt)")
}
This converts the string "100" into the integer 100 and prints the output. If optionalString were nil, hello, or 3.5, nothing would be printed.
Also consider using a guard statement.
You can unwrap it this way:
if let yourStr = str_VAR?.toInt() {
println("str_VAR = \(yourStr)") //"str_VAR = 100"
println(yourStr) //"100"
}
Refer THIS for more info.
When to use “if let”?
if let is a special structure in Swift that allows you to check if an Optional holds a value, and in case it does – do something with the unwrapped value. Let’s have a look:
if let yourStr = str_VAR?.toInt() {
println("str_VAR = \(yourStr)")
println(yourStr)
}else {
//show an alert for something else
}
The if let structure unwraps str_VAR?.toInt() (i.e. checks if there’s a value stored and takes that value) and stores its value in the yourStr constant. You can use yourStr inside the first branch of the if. Notice that inside the if you don’t need to use ? or ! anymore. It’s important to realise thatyourStr is actually of type Int that’s not an Optional type so you can use its value directly.
Try this:
if let i = str_VAR?.toInt() {
println("\(i)")
}

"if let" statement executed despite value being nil

I have an "if let" statement that is being executed, despite the "let" part being nil.
if let leftInc : Double? = self.analysis.inputs[self.btnLeftIncisor.dictionaryKey!]! {
println(leftInc)
let valueString : String = formatter.stringFromNumber(NSNumber(double: leftInc!))!
self.leftIncisorTextField?.text = valueString
self.btnLeftIncisor.associatedLabel?.text = valueString
}
// self.analysis.inputs is a Dictionary<String, Double?>
The inputs dictionary holds information entered by the user - either a number, or nil if they haven't entered anything in the matching field yet.
Under the previous version of Swift, the code was written as this:
if let leftInc : Double? = self.analysis.inputs[self.btnLeftIncisor.dictionaryKey!]?? {
and worked correctly.
I saw a similar question here, but in that instance the problem seemed to be the result of using Any?, which is not the case here.
Swift 2.2
In your if let you define another optional, that's why nil is a legitimate case. if let is intended mainly to extract (maybe) non optional value from an optional.
You might try:
if let leftInc : Double = self.analysis.inputs[self.btnLeftIncisor.dictionaryKey!].flatMap ({$0}) {
// leftInc is not an optional in this scope
...
}
Anyway I'd consider to not do it as a one liner but take advantage of guard case. Just in order to enhance readability. And avoid bang operator (!).
The if-let is for unwrapping optionals. You are allowing nil values by setting the type to an optional Double.
The if statement should be:
if let leftInc = self.analysis.inputs[self.btnLeftIncisor.dictionaryKey!] as? Double{
...
}
This will attempt to get an object out of inputs, if that fails it returns nil and skips it. If it does return something it will attempt to convert it to a Double. If that fails it skips the if statement as well.
if inputs is a dictionary like [Something:Double] then you don't need the last as? Double as indexing the dictionary will return a Double?
I recommend reading the swift book on optional chaining.
You could break it down further -
if let optionalDouble = self.analysis.inputs[self.btnLeftIncisor.dictionaryKey!], leftInc = optionalDouble {
....
}
as your dictionary has optional values - this way of writing it might make it clearer what's going on
if let k = dict["someKey"]{}, dict["someKey"] will be an object of type Any
this can bypass a nill
So do a typecast to get it correct like if let k = dict["someKey"] as! String {}

Swift optionals confusion

I'm having a bit of a hard time wrapping my head around optionals and why they are beneficial.
First off, are these two code block essentially equivalent?
Swift:
if let unwrappedName = p.name {
var greeting = “Hello “ + unwrappedName
} else {
var greeting = “Hello stranger”
}
Objective-C:
NSString *greeting;
if (p.name) {
greeting = [NSString stringWithFormat:#"Hello %#", p.name];
} else {
greeting = #"Hello stranger"
}
Second, this is coming from the Swift iBook, I don't get any errors on these but what exactly is the difference between them??
1.
let optionalSquare: Square? = Square(sideLength: 10, name: "Optional Square")
let sideLength = optionalSquare?.numberOfSides
2.
let optionalSquare: Square! = Square(sideLength: 10, name: "Optional Square")
let sideLength = optionalSquare?.numberOfSides
3.
let optionalSquare: Square? = Square(sideLength: 10, name: "Optional Square")
let sideLength = optionalSquare!.numberOfSides
Yes, optional types are pretty similar for reference types to Objective-C references. The key difference is that non optional type can never be "nil". If the type of returned a method you are calling is Object?, you are forced to unwrap it before using it. It is clear that the response can be "empty" and the compiler will force you to handle that.
If the type is not optional then you never need to check if it is nil before using. In addition, Optional work with value types like Int. You don't need to remember if the empty value is 0 or -1 or some other magic value.
You should use optional pretty much anytime you are thinking about using a magic sentinel value that means the absence of something, ie 0, -1, nil, empty list, empty string, etc. It not hard to remember what should be checked but it is easy to forget. With optional, you do not need to "double check" its not nil, even though it "should not be nil but maybe it is". Instead, of return -1 as an index of an object that was not found return Int?. Instead of throwing an exception, when a file is not found you can pass a an optional object.
Compare with optionals:
if let mapleSyrup = shoppingList.removeAtIndex(3) {
print "Bought /(mapleSyrup)"
}
To without optionals and not perfectly clear documentation:
var mapleSyrup = shoppingList.removeAtIndex(3)
if mapleSyrup != nil && mapleSyrup.isEmpty {
print "Bought /(mapleSyrup)"
}
The second question is answered well in the swift ibook here.
let optionalSquare: Square! = Square(sideLength: 10, name: "Optional Square")
Lets you use optionalSquare as if it was a normal variable and doesn't need to be unwrapped first.
let sideLength = optionalSquare!.numberOfSides
If you know optionalSquare has a value, you can force to be unwrapped and used with !.
This article starts with some great information on the problems with nil and the benefits of optional types.