10

I would like to return different views from a function, a Text or a VStack of a Text and a Button. Here's one of my attempts:

func buildResponseText2() -> some View {
    if (userData.success) {
        return VStack {
            Text("Well Done")
            Button("Play Again", action: {self.userData.reset()})
        }
    }
    return VStack {
        Text("Unlucky")
    }
}

This does not compile, I get the error

Function declares an opaque return type, but the return statements in its body do not have matching underlying types

Is there a way to return view containers like VStack with heterogeneous contents?

dumbledad
  • 16,305
  • 23
  • 120
  • 273

3 Answers3

11

Use type erasing AnyView as return type, as below

func buildResponseText2() -> AnyView {
    if (userData.success) {
        return AnyView(VStack {
            Text("Well Done")
            Button("Play Again", action: {self.userData.reset()})
        })
    }
    return AnyView(VStack {
        Text("Unlucky")
    })
}
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Asperi
  • 228,894
  • 20
  • 464
  • 690
10

You were close to the solution, actually. Indeed, you can use some View as return type this way:

func buildResponseText2() -> some View {
    Group {
        if userData.success {
            VStack {
                Text("Well Done")
                Button("Play Again", action: {self.userData.reset()})
            }
        } else {
            Text("Unlucky")
        }
    }
}
superpuccio
  • 11,674
  • 8
  • 65
  • 93
  • 3
    Feels like this is arguably a better solution than the accepted answer, although they're both similar. Avoids having to wrap each branch of the if-else content in an `AnyView` – less visual noise & code to change in future – Seb Jachec Jan 09 '20 at 19:28
2

Extract layout and Preserve the original type:

You should implement a custom view with a custom view builder so you can return different types. Also, you may want to remove the VStack from the return value, so you can decide to use a different layout later without any need to change the content.

So implement this:

struct StackContent: View {
    let success: Bool

    @ViewBuilder var body: some View {
        switch success {
        case true:
            Text("Well Done")
            Button("Play Again") { print("clicked") }
        case false:
            Text("Unlucky")
        }
    }
}

Then you can have this:

func buildResponseText2() -> some View {
    VStak { // <- you can change it to other layout containers
         StackContent(success: userData.success)
    }
}

Maybe you don't need the function at all anymore ;)

Note that there is no need to write @ViewBuilder from Swift 5.3 (Xcode 12)

Mojtaba Hosseini
  • 95,414
  • 31
  • 268
  • 278