if (variable assignment) syntax query (Swift) - swift

I am confused with what exactly this line,
if let fileList = fileList, let files = fileList.files
is doing. If I understand correctly, a conditional can only have a boolean as its parameter, so
let fileList = fileList, let files = fileList.files
should be either true or false. What exactly does this syntax do?
func updateDisplay() {
updateButtons()
if let fileList = fileList, let files = fileList.files {
if files.count == 0 {
output?.text = "Folder is empty"
table?.isHidden = true
} else {
table?.isHidden = false
table?.reloadData()
}
}
else {
output?.text = ""
table?.isHidden = true
}
}

The let a = b syntax used in an if statement is called optional binding. You can read more about it here. Essentially, if b is nil, the condition is treated as "false". If b is not nil, the condition is treated as "true", and a non optional constant a will now have the value that b has, and you can use it inside the if block.
Swift if statements also allow you to specify multiple conditions. All of them has to be "true" for the if block to be executed. These conditions doesn't have to be boolean expressions. They could be any of the following:
Boolean expressions
Optional Binding
#available check
case pattern matching
If you have two boolean expressions and you want to run some code if both of them are true, you could use && to join them together, but if you have 2 optionals that you want to bind, or one boolean expression and one optional binding, or any combination of the above, you'd need to use a comma to separate the conditions.

if let fileList = fileList, let files = fileList.files {
is actually an assignment statement with a test for "null". If the assignment succeeds, then carry out the code inside the braces. The boolean test for equality in swift is ==.

Related

How to do unwrappin inside If-let statement?

I am using if let for getting the object if its not nil. But I also need to check other condition as well i.e., if "treatmentContext.patientTreatment.canWritePermissions.contains(treatmentContext.pathPatientTreatment.owner". That I am able to do by putting comma after the first statement but here the issue is, I need to unwrap the value of treatmentContext.pathPatientTreatment.owner and here I don't know where exactly I need to unwrap that so that my if condition gets pass when it meets all the criteria.
Below is the code for reference.
if let treatmentContext = IoC.resolve(Treatment.self, from: .treatment), treatmentContext.patientTreatment.canWritePermissions.contains(treatmentContext.pathPatientTreatment.owner)
{
self.presentNavigation(isNew: isNew)
}
You already know you can separate the conditions with ,, so just do that again, but this time it's another optional binding clause:
if let treatmentContext = IoC.resolve(Treatment.self, from: .treatment),
let owner = treatmentContext.pathPatientTreatment.owner,
treatmentContext.patientTreatment.canWritePermissions.contains(owner) {
self.presentNavigation(isNew: isNew)
}
You can separate any number of optional binding clauses, Bool conditions, or case ... patterns with , in an if.

Is it possible to use a string variable in if condition in Swift?

I'm new to iOS development and wondering if I could pass a string variable inside if statement? Here's my pseudo code:
x = 1
func myFunc() -> String {
myString = "x == 1"
return myString
}
if(myfunc()) {
code i want to execute
}
I am currently getting the following error: "'String' is not convertible to 'Bool'"
Is there a way I can do this?
You should use a comparison operator for this.
if myString == myFunc() {
// your code
}
If statement always wants a condition that can return a bool value. i.e. true and false.
In your above code, you are not providing sufficient data to if statement so that it can calculate whether the result iss true or false.
When you compare it like if myString == myFunc() , if statement will compare the string and return true if string matches else false.
if the string matches, it will execute the code that is within if conditions scope. Otherwise it will calculate the else condition.
UPDATE1:
I see you have updated the question, so you want to check if myFunc() is empty or not?
For that you can compare it with empty string.
if myFunc() == "" {
// your code
}
UPDATE2:
Question: (asked in comment) instead of writing "if(x == 1)" i am trying to use a variable so my if statement is "if(stringVaraible)" where stringVariable = "x ==1". Basically I am asking if it is possible to turn a string into normal code
Answer: No, you can't do that. Swift is a compiled language, not interpreted like Ajax. Read more here: https://stackoverflow.com/a/30058875/8374890
It's very specific and clear that you can't use String as boolean. The approach you can take is well known like..
if(myString == "x == 1") {
code i want to execute
}
or
if(desiredString == myFunc()) {
code i want to execute
}

Why outside of block swift can't see value assigned to a uninitialized variable in the block?

What is the mechanism of declaring w/o value in Swift5 ? Does the first assign become the real declaration ?
And, should we avoid to declare without value in Swift?
var v:String;
if true {
v = "Hello"
print(v) // print "Hello" when without the print below
}
print(v) // variable 'v' used before being initialized
var v:String="";
if true {
v = "Hello"
print(v) // print "Hello"
}
print(v) // print "Hello"
Well, the message is not very helpful, and that's the problem. This pattern (which I call computed initialization) is perfectly legal and useful and — amazingly — you can even use let instead of var. But you must initialize the uninitialized variable by all possible paths before you use it. So you have:
var v:String
if true {
v = "Hello"
}
print(v) // error
But hold my beer and watch this:
var v:String
if true {
v = "Hello"
} else {
v = "Goodbye"
}
print(v) // fine!
Or even:
let v:String
if true {
v = "Hello"
} else {
v = "Goodbye"
}
print(v) // fine!
Amazing, eh?
Now, you might say: OK, but true will always be true so it's silly to make me fulfill the "all paths" rule. Too bad! The compiler insists anyway, and then lets you off later with a warning that the else won't be executed. But a warning lets you compile; an error doesn't. The truth is that your example is very artificial. But this is a real-life possibility:
let v:String
if self.someBoolProperty {
v = "Hello"
} else {
v = "Goodbye"
}
print(v) // fine!
Not only is this sort of thing legal, it is actually the pattern that Apple recommends under certain slightly tricky circumstances. For instance, it is used in Apple's own example code showing how to use the Swift 5 Result struct:
let result: Result<Int, EntropyError>
if count < AsyncRandomGenerator.entropyLimit {
// Produce numbers until reaching the entropy limit.
result = .success(Int.random(in: 1...100))
} else {
// Supply a failure reason when the caller hits the limit.
result = .failure(.entropyDepleted)
}
So this is because swift compiles your code and notices that your value var v:String; is undeclared before being used which makes it an "optional" value. Even though you are assigning it within the if statement, if you were to get rid of the true value it is possible that the if statement would never run therefore no value will ever be stored in v, thus it would be used before "assigned".
So to answer your question if you want your value to be an optional and possible empty value declare v as the following var v:String? if you would like it to be a non-optional value with a value always stored within v you should declare it as the following var v = "". Swift will interpret this declaration as a String.
To answer your second question, defining without values in swift is 100% allowed, it really just depends on how you want to handle your code. I use optional values in my code all the time, but I don't necessarily like optionals so i usually like to declare my values such as var v = "" that way if the value is empty my UI or whatever else i'm manipulating won't break. But if i need to ensure a value is present i will have to make my value optional so i can use an if statement to check whether it's a valid value or not.
Shorter version of what I'm trying to say is, you are receiving the compiler warning because you are declaring v as a non-optional value rather than an optional value.

Why doesn't swift allow for let-based decisions in ternaries?

It's possible to do the following in Swift:
let params: String
if let aString = someOptionalString {
params = "?someparam=\(aString)"
} else {
params = ""
}
However it would be much more concise if I could write it like:
let params = let aString = someOptionalString ? "?someparam=\(aString)" : ""
Or
let params = case let aString = someOptionalString ? "?someparam=\(aString)" : ""
However this doesn't compile in any way I could think about. Is this possible? If not, why not? And is there a way to suggest it to be implemented or can I only try to first add this myself to the Swift project and then propose the change to the community?
Because mapping an optional is a much more sensible choice:
let params = someOptionalString.map{ "?someparam\($0)" } ?? ""
To answer your question as given, conditional let only applies in an if statement or similar branching construct, everywhere else it doesn't have a value.
Normally when you have a pattern like:
if let x = y {
doSomething(x)
}
...what you're doing is declaring a new namespace context inheriting from the current one where x is defined; whether the code enters that context depends on whether the assigning expression evaluates to nil. Outside the block, x is not defined and it's an error to refer to it. If you like, think of it as a closure, something that might look like:
callIfNotNil(y, {x in doSomething(x)})
When you do a let otherwise, you are defining it in the current namespace, which means it can't not be defined on the other side of the ternary operator, so the best the compiler could give you is String! as a type, which would defer the nil check to runtime, largely defeating the point of using it.
In principle ternary could apply the same behaviour by defining an implicit block in the middle of the expression, but that's a recipe for confusion on the part of the programmer.
As for why let x = true; let y = x ? 1 : 2 is valid but let y = let x = true ? 1 : 2 isn't, there are some trivial precedence problems there, and with the let keyword being a compile-time feature not a runtime one it would be misleading to allow it mid-expression.

When filtering an array literal in swift, why does the result contain optionals?

A contrived example to be sure, but why is the result an array of optionals?
let r = [1,2,3].filter { sourceElement in
return !["1", "2"].contains { removeElement in
sourceElement == Int(removeElement)
}
}
print(r.dynamicType)
Either type casting the source array or assigning it to a variable returns an array of Ints.
let seq = [1,2,3]
let r2 = seq.filter { sourceElement in
return !["1", "2"].contains { removeElement in
sourceElement == Int(removeElement)
}
}
print(r2.dynamicType) // "Array<Int>\n"
Shouldn't both results be of the same type?
I don’t think it’s necessarily a bug though it is confusing. It’s a question of where the promotion to optional happens to make the whole statement compile. A shorter repro that has the same behavior would be:
let i: Int? = 1
// x will be [Int?]
let x = [1,2,3].filter { $0 == i }
Bear in mind when you write nonOptional == someOptional the type of the lhs must be promoted to optional implicitly in order for it to work, because the == that you are using is this one in which both sides must be optional:
public func ==<T>(lhs: T?, rhs: T?) -> Bool
The compiler needs to promote something in this entire statement to be an optional, and what it chose was the integer literals inside [1,2,3]. You were instead expecting the promotion to happen at the point of the ==, so you could compare the non-optional sourceElement with the optional result of Int(_:String), but this isn’t necessarily guaranteed (not sure to what extent the ordering/precedence of these promotions is specced vs just the way the compiler was coded…)
The reason this doesn’t happen in the two-line version is when you write as one line let seq = [1,2,3], the type of seq is decided there. Then on the next line, the compiler doesn’t have as much latitude, therefore it must promote sourceElement to be an Int? so it can be compared with Int(removeElement) using ==.
Another way of making the code perform the conversion at the point you expect would be:
let r = [1,2,3].filter { sourceElement in
return !["1", "2"].contains { (removeElement: String)->Bool in
// force the optional upgrade to happen here rather than
// on the [1,2,3] literal...
Optional(sourceElement) == Int(removeElement)
}
}