On if let bindings in SwiftUI

marionauta

Mario Nachbaur

Posted on April 11, 2020

On if let bindings in SwiftUI

Update: with Xcode 12, we can write if let bindings without problems. This code is now valid:

if let address = library.address {
    Text(address)
}
Enter fullscreen mode Exit fullscreen mode

I'll leave the post up for historical context.


Recently I was building an app that lists public libraries in my home city. In early stages, the main view was just a list view that displays the library name and address. Something like this:

Screenshot showing a quick prototype for my app

Quick prototype of my app

As you can see, some libraries don't have an address, because of some unrelated reasons. And if the address isn't present, the name should be vertically centred in the row.

A library not having an address is translated into my data model as an optional String. Here's a simplified version of my Library model:

struct Library {
    let id: String
    let name: String
    let address: String?
}
Enter fullscreen mode Exit fullscreen mode
Neat, eh?

Initial approach

If a library doesn't have an address, I won't render a Text component for it, simple as that. And the easiest way for me to do that was using an if let binding:

VStack {
    Text(library.name)
    if let address = library.address {
        Text(address)
    }
}
Enter fullscreen mode Exit fullscreen mode
This WON'T compile!

As it turns out, SwiftUI doesn't like this. The compiler tells us that Closure containing control flow statement cannot be used with function builder 'ViewBuilder'. I beg your pardon?

How else am I supposed to conditionally render views in my app if "control flow statement cannot be used" to build it? After a moment of panic, I tested using a normal if:

VStack {
    Text(library.name)
    if library.address != nil {
        Text(library.address!)
    }
}
Enter fullscreen mode Exit fullscreen mode
Force unwrapping make kittens cry

And surprisingly that worked... wtf. Needless to say, I wasn't very exited with that solution, since it requires force unwrapping an optional value. I knew that there was another way.

Current solution

One of the key features of SwiftUI is that if we add a nil component to our view, it won't show up, it's just ignored. So, what's the easiest way to convert a conditional string into a conditional Text? That's right, using map.

Here we map over the possibly nil address, without needing to force unwrap it, as the closure will only be executed if there was a value in the first place. This, however, seems like a hack. But hey, It works.

VStack {
    Text(library.name)
    library.address.map { address in
        Text(address)
    }
}
Enter fullscreen mode Exit fullscreen mode
Heh, mapping an optional... I can still hear my CS professor talking about monads.

Let's wait and see

As I was reading some discussions about this issue, I found out that SwiftUI builds views using a struct named ViewBuilder — remember the error I was getting before? If we peek its definition (cmd + shift + o in XCode to go to any class definition) we can clearly see that it supports if statements, but nothing more.

SwiftUI is not event a year old, and we know Apple only introduces big features once a year. So here's hoping that when the next version of SwiftUI shows up, they add if let support.

💖 💪 🙅 🚩
marionauta
Mario Nachbaur

Posted on April 11, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related