May 3, 2020

Conditionally render a different View

Well, most of the SwiftUI beginner do try this which compiler won't compile


func textOrImage(_ text: Bool) -> some View {
    text ? Text("Hello") : Image(systemName: "headphones")
}

If you don't understand why, read about some keyword,

in short it is a shorthand to avoid writing long generic types but still compiler will check that function returns the same concrete type from all return paths.


Also don't even try to do this

func textOrImage(_ text: Bool) -> Group {
    text ? Group { Text("Hello") } : Group { Image(systemName: "headphones") }
}

Group is a generic so a concrete type is Group<Text> or Group<Image>, but you can't return both.


Type erased view AnyView can be used here.

func textOrImage(_ text: Bool) -> AnyView {
    text ? AnyView(Text("Hello")) : AnyView(Image(systemName: "headphones"))
}

But important to note this from the documentation.

"Whenever the type of view used with an AnyView changes, the old hierarchy is destroyed and a new hierarchy is created for the new type."


One implication of this is that, as SwiftUI system need to discard the whole view hierarchy, in some case it mess with the animation (more on it later).


Another way is to do something like this

func textOrNil(_ text: Bool) -> Text? {
    guard text else { return nil }
    return Text("Hello")
}

func imageOrNil(_ text: Bool) -> Image? {
    guard text else { return Image(systemName: "headphones") }
    return nil
}

It is perfectly fine to then use these methods like below

var isText: Bool
VStack {
    textOrNil(isText)
    imageOrNil(isText)
}

//which is same as writing
VStack {
    ViewBuilder.buildIf(textOrNil(isText))
    ViewBuilder.buildIf(imageOrNil(isText))
}

Tagged with: