Is there a way to include multiple conditions in a guard statement of Swift?
For example, if I want to check two optional values are nil using a guard, how should I do it using single guard statement?
Check this code
func demo(){
var str = [String: String]()
str["status"] = "blue"
str["asd"] = nil
guard let var2 = str["asd"], let var1 = str["status"]
else
{
print("asdsfddffgdfgdfga")
return
}
print("asdasdasd")
}
Guard will check one by one condition. If the first is true then it will check the next. Otherwise, it will execute the else part.
To answer Prabhav's question, yes, you are correct, each condition in a guard statement must be true in order to proceed (i.e., not go into the else block). In this sense, it is indeed like separating conditions with AND logic.
You can implement OR logic, not by using commas, but by using a Boolean condition:
guard
true || false // this guard statement evaluates to true
else
{
print("no, not all values in the guard were true")
return
}
print("yes, all of the values in the guard were true") // this is printed
or a combination of OR and AND logic, by using a combination of Boolean conditions and optional bindings:
let testString: String? = nil
guard
true || false,
let x = testString, // this guard statement evaluates to false
true
else
{
print("no, not all values in the guard were true") // this is printed
return
}
print("yes, all of the values in the guard were true")
This summary from Apple, written about optional bindings in if statements is equally applicable to guard statements:
You can include as many optional bindings and Boolean conditions in a
single if statement as you need to, separated by commas. If any of the
values in the optional bindings are nil or any Boolean condition
evaluates to false, the whole if statement’s condition is considered
to be false. The following if statements are equivalent:
if let firstNumber = Int("4"), let secondNumber = Int("42"),
firstNumber < secondNumber && secondNumber < 100
{
print("\(firstNumber) < \(secondNumber) < 100")
} // Prints "4 < 42 < 100"
if let firstNumber = Int("4")
{
if let secondNumber = Int("42")
{
if firstNumber < secondNumber && secondNumber < 100
{
print("\(firstNumber) < \(secondNumber) < 100")
}
}
} // Prints "4 < 42 < 100"
Related
How can I use guard let like:
guard let value = vm.value1 || let value = vm.value2 else { return }
I need to check value1, If it has value, continue to work with it, else check value2, and work with it, else: quit. Only one can have value.
The semantics you are describing seems to be:
guard let value = vm.value1 ?? vm.value2 else { return }
If vm.value1 is not nil, value would be bound to its value, and the code after the guard statement would be executed.
Otherwise, if vm.value2 is not nil, value would be bound to its value, and the code after the guard statement would be executed.
Otherwise, return would be executed.
Similarly, multiple lets could be used to achieve something similar to the semantics of the logical "AND":
guard let value1 = vm.value1, let value2 = vm.value2 else { return }
The code after the guard statement is only executed if vm.value1 is not nil, and vm.value2 is not nil, and value1 and value2 are bound to the corresponding values.
Also note that you can mix arbitrary boolean conditions with the let bindings too:
guard let value = vm.value1, someBool || someOtherBool else { return }
You can't use logical operator with guard statement
But there is another way of performing AND operator
guard let value = vm.value1,
let value = vm.value2 else {
return
}
And OR operator functionality can be achieve by using ternary operator with guard statement
guard let value = ((vm.value1 != nil) ? vm.value1 : vm.value2 else {
return
}
And you can use the value after else statement
I currently have the guard statement:
guard let designationQuota = Defaults.quotas.value?.designationQuota, designationQuota > 0 else {
return AppDelegate.shared.presentNoDesignationQuotaWarning()
}
however I only want to do the guard block if the variable needsQuota == true. I want to skip the guard statement if needsQuota == false. Is there a nicer way of doing this over than an if statement with a return?
EDIT:
How do I simplify this into a single guard?
if needsQuota {
guard let designationQuota = Defaults.quotas.value?.designationQuota, designationQuota > 0 else {
return AppDelegate.shared.presentNoDesignationQuotaWarning()
}
}
How about :
guard !needsQuota ||
(Defaults.quotas.value?.designationQuota.map { $0 > 0 } == true) else {
return AppDelegate.shared.presentNoDesignationQuotaWarning()
}
The problem is that you want to continue execution differently in case your if condition fails or in case your guard fails, so you cannot really combine them into a single guard. However, you could combine the two conditions into an if statement by putting the negated version of your guard condition in the if statement.
if needsQuota && (Defaults.quotas.value?.designationQuota ?? 0 <= 0) {
return AppDelegate.shared.presentNoDesignationQuotaWarning()
}
Wouldn‘t this do the trick?
guard needsQuota, let designationQuota = Defaults.quotas.value?.designationQuota, designationQuota > 0 else {
return AppDelegate.shared.presentNoDesignationQuotaWarning()
}
Is there any difference between these two syntaxes? If not, any benefit?
if let userName = userNameTextField.text where userName.characters.count > 0,
let password = passwordTextField.text where password.characters.count > 0,
let confirmation = confirmationTextField.text where confirmation == password
else {
return false
}
and:
if userNameTextField.text?.characters.count > 0 &&
passwordTextField.text?.characters.count > 0 &&
confirmationTextField.text == passwordTextField.text
{
return false
}
First of all, note that where clauses in optional binding conditions is deprecated in Swift 3.0, replaced by ,.
Evolution Proposal SE-0099: Restructuring Condition Clauses
I.e.
let opt: Int?
/* Swift < 3 */
if let opt = opt where opt == 42 { /* ... */ }
/* Swift >= 3 */
if let opt = opt, opt == 42 { /* ... */ }
Secondly, your second example block wont compile in Swift >= 3.0, as the optional result from the optional chaining is not unwrapped; comparing optionals to literals has been removed in Swift 3.0 (thanks #MartinR), as per the following implemented proposals:
Evolution proposal SE-0121: Remove Optional Comparison Operators
Evolution proposal SE-0123: Disallow coercion to optionals in operator arguments
userNameTextField.text?.characters.count > 0 &&
/* ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
this is an optional that, for Swift >= 3.0, needs to be unwrapped
prior to comparing it to the integer literal */
Now, proceeding to answer your "is there any difference ..." question, assuming we look at your two options fixed for Swift 3.
You could choose to bind the value of the optional String property text of userNameTextField if you'd like to use it in the if block that follows, or
If youd only want to ascertain that the text property is not nil or empty (""), you could omit the binding in favour or simply checking its character.count
E.g.:
struct Foo {
var bar: String?
}
var foo = Foo()
/* if you want to use foo.bar within the if block */
if let str = foo.bar, str.characters.count > 0 {
// do something with str ...
print(str)
}
/* if you only need to ascertain foo.bar is not nil
and not empty */
if (foo.bar?.characters.count ?? 0) > 0 {
// do something with str ...
print("not empty")
}
// alternatively ... (not nil or empty)
if !(foo.bar?.isEmpty ?? true) {
// do something with str ...
print("not empty")
}
If you simply want to ascertain the latter and return false in case the ascertation fails, you could prefer using a guard statement instead of if:
guard (foo.bar?.characters.count ?? 0) > 0 else { return false }
// ... if no false return, proceed with app flow here ...
// alternatively ...
guard !(foo.bar?.isEmpty ?? true) else { return false }
Neither version works in Swift 3. And I think in the first version, you meant to use guard instead of let.
With Swift 3, you want to do the following:
guard
let userName = userNameTextField.text,
let password = passwordTextField.text,
let confirmation = confirmationTextField.text,
userName.characters.count > 0,
password.characters.count > 0,
confirmation == password
else {
return false
}
There are problems with using your approach in Swift 3 as explained by some of the other answers. But if you are using Swift 2, the following is my answer to your question.
The two are exactly the same with only one difference. Both approaches are testing for a nil value and then introducing a boolean test if the value is not nil. For example in your line,
if let userName = userNameTextField.text where userName.characters.count > 0
You are testing to see if userNameTextField is not nil and then you are testing if its count is greater than zero. The same thing applies this line,
if userNameTextField.text?.characters.count > 0
except it is much shorter and readable.
Because both approaches are within an 'if' statement, they both evaluate to true or false and achieve the same purpose.
However, what separates them is that with the 'where' clause, you can introduce a Boolean test after a binding or pattern, regardless of whether or not there's an underlying semantic link between the two. So I can basically do this with the 'where' clause,
if let userName = userNameTextField.text where age > 0
There is no semantic link between userNameTextField and age. As you can imagine, that might not be what you intended and this can quickly lead to errors.
To continue to the next screen, a patron must have one of two identifiers. The code I've got to do this is:
let identifier1Entered = !patron.identifier1.isEmpty
let identifier2Entered = patron.identifier2 != nil && !patron.identifier2!.isEmpty
guard identifier1Entered || identifier2Entered else { return }
But it's not Swifty, I'm force-unwrapping the optional identifier2, because I don't want to expand this to a longer, and IMO messier
var identifier2Entered = false
if let identifier2 = patron.identifier2 where !identifier2.isEmpty {
identifier2Entered = true
}
What I thought might work is just taking the expression out of the if statement, like:
let id2Entered = let identifier2 = patron.identifier2 where !identifier2.isEmpty
or
let id2Entered = case .Some(let id2) = patron.identifier2 where !id2.isEmpty
But it appears that these expressions are only allowed within if statements.
The other more Swifty solution I thought of is this:
let identifier1Entered = !patron.identifier1.isEmpty
guard let id2 = patron.identifier2 where !id2.isEmpty || identifier1Entered
else { return }
But it requires that identifier2 is not nil, which is incorrect, and as far as I know, there's no way to use optional binding with || in if or guard statements. Not to mention that I feel it's less clear and readable than the force-unwrapping.
Anyone have a clear, more Swifty solution?
Two possible solutions using optional chaining:
let identifier2Entered = patron.identifier2?.isEmpty == false
let identifier2Entered = !(patron.identifier2?.isEmpty ?? true)
If patron.identifier2 is nil then patron.identifier2?.isEmpty
evaluates to nil, and you'll get false as a result.
This is not a question about optional arrays, as one can see in the answers.
I like using guard because it makes your intensions clear. I've used it both for the optional version like this...
guard let c = MyOptionalArray else { return }
as well as for more traditional bounds checking on non-optionals...
guard MyArray.count > 0 else { return }
But now I'd like to use that count in following code. So I did...
guard let c = MyArray.count > 0 else { return }
which doesn't work, obviously, so I did what should...
guard let c = parts.count where c > 1 else { return }
But that says Initializer for conditional binding must have Optional type, not 'Int'. Now I understand the error, and tried a bunch of seemingly obvious changes to the format, but no go. Is there no way to use guard as an assignment on a non-optional value? This seems like something it should be able to do.
If you throw a case in there, it'll work. So as follows:
guard case let c = parts.count where c > 1 else { return }
You can initialize an optional wrapping the non-optional:
guard let count = Optional([42].count), count > 0 else {
return
}
guard let count = .some([42].count), count > 0 else {
return
}
or cast it to an optional:
guard let count = [42].count as Int?, count > 0 else {
return
}
As mentioned in other answers, guard case let also works:
guard case let count = [42].count, count > 0 else {
return
}
guard case let count = [42].count where count > 0 else {
return
}