114

We know that we can use an if let statement as a shorthand to check for an optional nil then unwrap.

However, I want to combine that with another expression using the logical AND operator &&.

So, for example, here I do optional chaining to unwrap and optionally downcast my rootViewController to tabBarController. But rather than have nested if statements, I'd like to combine them.

if let tabBarController = window!.rootViewController as? UITabBarController {
    if tabBarController.viewControllers.count > 0 {
        println("do stuff")
     }
 }

Combined giving:

if let tabBarController = window!.rootViewController as? UITabBarController &&
    tabBarController.viewControllers.count > 0 {
        println("do stuff")
     }
}

The above gives the compilation error Use of unresolved identifier 'tabBarController'

Simplifying:

if let tabBarController = window!.rootViewController as? UITabBarController && true {
   println("do stuff")
}

This gives a compilation error Bound value in a conditional binding must be of Optional type. Having attempted various syntactic variations, each gives a different compiler error. I've yet to find the winning combination of order and parentheses.

So, the question is, is it possible and if so what is correct syntax?

Note that I want to do this with an if statement not a switch statement or a ternary ? operator.

Bryan Chen
  • 45,816
  • 18
  • 112
  • 143
Max MacLeod
  • 26,115
  • 13
  • 104
  • 132
  • It is always a good idea to provide the error messages in the question. – zaph Aug 08 '14 at 11:48
  • More simple statement for people who like to experiment with this: `var foo : Int? = 10; if let bar = foo { if bar == 10 { println("Great success!") }}` – DarkDust Aug 08 '14 at 11:55
  • The error message when using `if let bar = foo && bar == 10` is `Use of unresolved identifier "bar"` (on the second `bar`, of course). – DarkDust Aug 08 '14 at 11:57
  • just a note that I was able to shorten the above example using Objective C's firstObject as follows: if let navigationController = tabBarController.viewControllers.bridgeToObjectiveC().firstObject as? UINavigationController { – Max MacLeod Aug 08 '14 at 15:41
  • 1
    1. `bridgeToObjectiveC` is gone in beta 5 (and was quite possibly never intended for general use). 2. There's a `first` method on Swift's built-in `Array` type anyway. – rickster Aug 09 '14 at 01:22

6 Answers6

168

As of Swift 1.2, this is now possible. The Swift 1.2 and Xcode 6.3 beta release notes state:

More powerful optional unwrapping with if let — The if let construct can now unwrap multiple optionals at once, as well as include intervening boolean conditions. This lets you express conditional control flow without unnecessary nesting.

With the statement above, the syntax would then be:

if let tabBarController = window!.rootViewController as? UITabBarController where tabBarController.viewControllers.count > 0 {
        println("do stuff")
}

This uses the where clause.

Another example, this time casting AnyObject to Int, unwrapping the optional, and checking that the unwrapped optional meets the condition:

if let w = width as? Int where w < 500
{
    println("success!")
}

For those now using Swift 3, "where" has been replaced by a comma. The equivalent would therefore be:

if let w = width as? Int, w < 500
{
    println("success!")
}
Max MacLeod
  • 26,115
  • 13
  • 104
  • 132
65

In Swift 3 Max MacLeod's example would look like this:

if let tabBarController = window!.rootViewController as? UITabBarController, tabBarController.viewControllers.count > 0 {
    println("do stuff")
}

The where was replaced by ,

ph1lb4
  • 1,982
  • 17
  • 24
  • 11
    So , is for && what is there for || ? – jeet.chanchawat Sep 29 '16 at 12:40
  • 10
    @jeet.chanchawat logical OR doesn't make sense in this context. `if let` can only proceed if the optional successfully unwrapped and is assigned to the new variable name. If you said `OR ` then you could easily bypass the whole purpose of the `if let`. For logical OR, just do a normal `if` and inside the body deal with the fact that the first object is still optional at that point (not unwrapped). – jhabbott Jan 05 '17 at 01:26
37

Max's answer is correct and one way of doing this. Notice though that when written this way:

if let a = someOptional where someBool { }

The someOptional expression will be resolved first. If it fails then the someBool expression will not be evaluated (short-circuit evaluation, as you'd expect).

If you want to write this the other way around it can be done like so:

if someBool, let a = someOptional { }

In this case someBool is evaluated first, and only if it evaluates to true is the someOptional expression evaluated.

par
  • 17,361
  • 4
  • 65
  • 80
6

Swift 4, I will use,

let i = navigationController?.viewControllers.index(of: self)
if let index = i, index > 0, let parent = navigationController?.viewControllers[index-1] {
    // access parent
}
Sazzad Hissain Khan
  • 37,929
  • 33
  • 189
  • 256
4

It is not possible.

From Swift grammar

GRAMMAR OF AN IF STATEMENT

if-statement → if ­if-condition­ code-block­ else-clause­opt­

if-condition → expression­ | declaration­

else-clause → else­ code-block­ | else­ if-statement­

The value of any condition in an if statement must have a type that conforms to the BooleanType protocol. The condition can also be an optional binding declaration, as discussed in Optional Binding

if-condition must be expression­ or declaration­. You can't have both expression and declaration.

let foo = bar is a declaration, it doesn't evaluate to a value that conforms to BooleanType. It declares a constant/variable foo.

Your original solution is good enough, it is much more readable then combining the conditions.

Bryan Chen
  • 45,816
  • 18
  • 112
  • 143
  • but surely "if let" - which is valid Swift syntax - is both an expression and a declaration? – Max MacLeod Aug 11 '14 at 08:32
  • @MaxMacLeod `let foo = bar` is declaration not expression. you can't do `let boolValue = (let foo = bar)` – Bryan Chen Aug 11 '14 at 21:49
  • ok but to quote the Swift guide, page 11, the example is: if let name = optionalName. optionalName must be an expression whereby if optionalName is not an optional, it evaluates to true. The second part of the statement then kicks in whereby the unwrapped optional is assigned to name. So, question is, if "optionalName is not an optional" is an expression, can it be expanded with a logical AND. Btw I think you are probably correct in that it cannot be done. – Max MacLeod Aug 12 '14 at 08:37
1
if let tabBarController = window!.rootViewController as? UITabBarController ,
               tabBarController.viewControllers?.count ?? 0 > 0 {
                    println("do stuff")   
              }
    }