31

In SwiftUI there's frequently a need to display an "empty" view based on some condition, e.g.:

struct OptionalText: View {
  let text: String?

  var body: some View {
    guard let text = text else { return }

    return Text(text) 
  }
}

Unfortunately, this doesn't compile since the body of guard has to return some view, that is an "empty" view when text is nil. How should this example be rewritten so that it compiles and renders an "empty" view when text is nil?

Max Desiatov
  • 5,087
  • 3
  • 48
  • 56

4 Answers4

32

You have to return something. If there is some condition where you want to display nothing, "display" an...EmptyView ;)

var body: some View {
    Group {
        if text != nil {
            Text(text!)
        } else {
            EmptyView()
        }
    }
}

The SwiftUI DSL will require you to wrap the if/else in a Group and the DSL has no guard/if let nomenclature.

Procrastin8
  • 4,193
  • 12
  • 25
16

As of Xcode 12 beta 2 the Group view is no longer needed and if let declarations are supported, so the resulting body can be a bit more succint:

var body: some View {
    if let text = text {
        Text(text)
    } else {
        EmptyView()
    }
}
Max Desiatov
  • 5,087
  • 3
  • 48
  • 56
  • 1
    Actually now you can just leave off the `else ` entirely. `EmptyView` is automatically the else condition in case the `if` fails. – Procrastin8 Dec 14 '21 at 16:10
10

You can use the @ViewBuilder. Then you don't even need an EmptyView:

@ViewBuilder
var body: some View {
    if let text = text {
        Text(text)
    }
}

Note that here you use @ViewBuilder to just build your view. If you want to learn more about how it's done behind the scenes, please see the below answer:

pawello2222
  • 46,897
  • 22
  • 145
  • 209
  • 3
    A pedantic note but you absolutely are "returning" something, the function has a delcared return type. The fact you don't have to explicitly `return` something is actually a result of https://github.com/apple/swift-evolution/blob/master/proposals/0255-omit-return.md. The SwiftUI DSL and `@ViewBuilder` make the building of views a single statement. – Procrastin8 Feb 09 '21 at 21:17
  • @Procrastin8 Yes, I'm aware of it, what I meant was that we don't return anything *directly*, ie. using the *return* keyword. But your explanation might be clearer for future readers. – pawello2222 Feb 09 '21 at 22:42
  • I would love to understand _why_ this works. You have a variable that must return an opaque type, but you only return it under some conditions. Does @ViewBuilder cause it to return `EmptyView` by default? – Christopher Pickslay Apr 08 '21 at 02:15
  • @ChristopherPickslay The best explantion you can find here: [What enables SwiftUI's DSL?](https://stackoverflow.com/q/56434549/8697793). – pawello2222 Apr 08 '21 at 21:55
1

Here's the shortest version of your code (if let shorthand declared for Swift 5.7+) that you can use in Xcode 14.0 and higher. This kind of optional binding shadows an existing variable.

struct ContentView: View {       
    @State var text: String? = nil

    var body: some View {   
        if let text {
            Text(text)
        }
    }
}

If you choose to use the guard let shorthand statement, implement this version:

struct ContentView: View {
    
    @State var text: String? = nil

    var body: some View {
        nonOptionalText(text)
    }
    
    func nonOptionalText(_ text: String?) -> some View {
        guard let text else { return Text("") }
        return Text(text)
    }
}
Andy Jazz
  • 49,178
  • 17
  • 136
  • 220