I'm stumped with this @ViewBuilder on var body: some View compile error: Cannot convert return expression of type '_ConditionalContent<Text, Text>' to return type 'some View'

I learned from this forum a while ago I can add @ViewBuilder to my view's var body: some View {} and use if/else expression inside. But I'm getting this compile error which I cannot understand:

This is how I want to write my code, but compile error:

struct MyIfElseView: View {
    @State private var flag = true

    @ViewBuilder
    var body: some View {    // <-- Compile error here "Cannot convert return expression of type '_ConditionalContent<Text, Text>' to return type 'some View'"
        if flag {
            Text("aaa")
        } else {
            Text("bbb")
        }
    }
}

This, which. is exactly the same semantically, is okay:

struct MyIfElseView: View {
    @State private var flag = true

    var aaa: some View {
        Text("aaa")
    }

    var bbb: some View {
        Text("bbb")
    }

    @ViewBuilder
    var body: some View {
        if flag {
            aaa
        } else {
            bbb
        }
    }
}

And most baffling, sticking my if/else inside a Group is fine:

struct MyIfElseView: View {
    @State private var flag = true

    var body: some View {
        Group {
            if flag {
                Text("aaa")
            } else {
                Text("bbb")
            }
        }
    }
}

So what exactly going on?

Hello :wave:
I tried to run your code with this edit, and it returned a different, strange error:

struct MyIfElseView: View {
    @State private var flag = true

    @ViewBuilder
    var body: _ConditionalContent<Text, Text> { 
        if flag {
            Text("aaa")
        } else {
            Text("bbb")
        }
    }
}

Output:

error: ViewBuilder on body.xcplaygroundpage:10:17: error: generic parameter 'FalseContent' could not be inferred
        if flag {
                ^

Looks like type information is lost somehow? There has been some improvements on function builders, so maybe we can find something relevant there?

This seems to be a bug that's recently been fixed. This code compiles using Swift master, and I suspect this works on the 5.2 branch as well because many of the function builder and type checking improvements that @TizianoCoroneo mentioned are in the 5.2 branch.

If you'd like to test it out, you can download a recent Swift 5.2 toolchain from Swift.org - Download Swift

2 Likes

Hi Holly, thank you for checking this out. You've just restored my sanity :sunglasses:

1 Like

I got this error after switching to the 5.2 toolchain:

Command Ld failed with a nonzero exit code

Even with this error, I can compile my code now without error.

Question: how often I should update the 5.2 toolchain? Everyday?

You can’t submit an app built by a development snapshot to the App Store, so they’re really just for Swift contributors (including people who want to see if the bug they reported has been fixed) and people who want to learn about what’s coming in future Swift versions (and possibly write long, detailed articles about it). Most people who write Swift apps for Apple platforms should stick to the XcodeDefault toolchain for all of their real work.

4 Likes

Can you link the relevant PR if you know it? I'm trying to learn as much as possible to contribute one day :slight_smile:

1 Like

You can workaround it for now by using ViewBuilder.buildEither methods directly.

2 Likes

There were many PRs that contributed to the type checking improvements - I don't know which one in particular fixed this bug. However, if you're interested in contributing to type checking in the compiler, I recommend reading our recent blog post about the New Diagnostic Architecture, and our documentation on the type checker. These resources will give you a good overview of how the expression type checker works!

1 Like

:frowning: I couldn't figure out how to manually transform the if/else to direct call ViewBuilder.builderEither(). Please show how? Thanks!

Also, is there any way to deal with this temporary workaround for now that we know the next Swift fix? Something like this:

#if (SwiftVersion < 52)
    // use workaround
    ViewBuilder.buildEither(....)
#else 
    #message("You can remove the workaround now at __FILE__ __LINE__")
    // use proper code
    if flag {
         Text("aaa")
    else {
        Text("bbb")
    }
#endif

Quick question why do you want to wrap the view if it‘s Text in both cases any way?

Version one:

var text: some View {
  if flag {
    return Text("A")
  } else {
    return Text("B")
  }
} 

Version two:

var conditonalText: some View { 
  typealias MyView = _ConditionalContent<Text, Text>
  if flag {
    return ViewBuilder.buildEither(first: Text("A")) as MyView
  } else {
    return ViewBuilder.buildEither(second: Text("B")) as MyView
  }
}

In such case the former would be preferred over the latter as SwiftUI won‘t need to remove and recreate a new view for Text.

2 Likes

It's just a simplified example, my actual code is three different kinds of Gradient's:

var colorSetting: some View {
    if setting == .gold {
        goldColorLinearGradient
    } else if setting == .psych {
        psychRadialGradient
    } else {
        dayNightAngularGradient
    }
}

I'm actually facing a bit of a problem: the gradients are also ShapeStyle, what I really need is this:

var colorSetting: some ShapeStyle & View {
    ... 😳 make a type erase AnyShapeStyleAndView()?             <<-- option 1
    ... or build some ConditionalShapeStyleAndView<xxx, yyy>     <<-- option 2
}

I cannot figure out how to implement ShapeStyle, so as now, I don't know how to do either options. Anyway, for now, I use them as View, but later I'll also need to use these thing with .strokeBorder(), which takes a ShapeStyle as parameter.

Anyway, looking at your "Version two" is very educational. It shows me how things can be built by hand...

Thanks!

2 Likes