NEW: My new book Pro SwiftUI is out now – level up your SwiftUI skills today! >>

How to return different view types

Paul Hudson    @twostraws   

Updated for Xcode 14.2

The body property of any SwiftUI automatically gets the ability to return different views thanks to a special attributed called @ViewBuilder. This is implemented using Swift’s result builder system, and it understands how to present two different views depending on our app’s state.

However, this same functionality isn’t automatically everywhere, which means any custom properties you make must return the same view type.

There are four ways you can fix this. The first option is to wrap your output in a group, so that no matter whether you send back an image or a text view they both go back in a group:

struct ContentView: View {
    var tossResult: some View {
        Group {
            if Bool.random() {
                Image("laser-show")
                    .resizable()
                    .scaledToFit()
            } else {
                Text("Better luck next time")
                    .font(.title)
            }
        }
        .frame(width: 400, height: 300)
    }

    var body: some View {
        VStack {
            Text("Coin Flip")
                .font(.largeTitle)

            tossResult
        }
    }
}

Download this as an Xcode project

The second is to use a type-erased wrapper called AnyView that we can return:

struct ContentView: View {
    var tossResult: some View {
        if Bool.random() {
            return AnyView(Image("laser-show").resizable().scaledToFit())
        } else {
            return AnyView(Text("Better luck next time").font(.title))
        }
    }

    var body: some View {
        VStack {
            Text("Coin Flip")
                .font(.largeTitle)

            tossResult
                .frame(width: 400, height: 300)                
        }
    }
}

Download this as an Xcode project

If you haven’t heard of this concept, it effectively forces Swift to forget about what specific type is inside the AnyView, allowing them to look like they are the same thing. This has a performance cost, though, so don’t use it often.

Although both Group and AnyView achieve the same result for our layout, between the two it’s generally preferable to use Group because it’s more efficient for SwiftUI.

A third option is to apply the @ViewBuilder attribute yourself to any properties that need it, like this:

struct ContentView: View {
    @ViewBuilder var tossResult: some View {
        if Bool.random() {
            Image("laser-show")
                .resizable()
                .scaledToFit()
        } else {
            Text("Better luck next time")
                .font(.title)
        }
    }

    var body: some View {
        VStack {
            Text("Coin Flip")
                .font(.largeTitle)

            tossResult
                .frame(width: 400, height: 300)                
        }
    }
}

Download this as an Xcode project

That works, but honestly if you find yourself reaching for @ViewBuilder you should question whether you’re trying to put too much into one view.

The fourth solution, and the one that works best the majority of the time, is to break up your views into smaller views, then combine them together as needed:

struct TossResult: View {
    var body: some View {
        if Bool.random() {
            Image("laser-show")
                .resizable()
                .scaledToFit()
        } else {
            Text("Better luck next time")
                .font(.title)
        }
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            Text("Coin Flip")
                .font(.largeTitle)

            TossResult()
                .frame(width: 400, height: 300)
        }
    }
}

Download this as an Xcode project

This works particularly well to help break apart logic and layout, and also has the benefit of making your views more reusable elsewhere in your app. SwiftUI will automatically collapse your view hierarchy, so there is no meaningful performance difference when you break up a view.

The text “Coin flip” over a successful outcome with lots of lasers, as well as an unsuccessful outcome with the words “Better luck next time”.

Hacking with Swift is sponsored by Essential Developer

SPONSORED From March 20th to 26th, you can join a FREE crash course for mid/senior iOS devs who want to achieve an expert level of technical and practical skills – it’s the fast track to being a complete senior developer!

Click to save your free spot now

Sponsor Hacking with Swift and reach the world's largest Swift community!

Similar solutions…

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.5/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.