Exploring SwiftUI Shapes: Utilizing @ViewBuilder for Dynamic View Creation

In this post, we explore how to use the @ViewBuilder annotation. It’s particularly useful when we need a function to return a View. While it’s a simple concept, it’s a classic example of something straightforward yet not always well-known.

In our example, we aim to showcase all the basic shapes that can be created in SwiftUI, including Rectangle, RoundedRectangle, Uneven Rounded Rectangle, Ellipse, Capsule, and Circle, as demonstrated here:

First, create an enum to represent the different shapes:

enum Shapes {
    case Rectangle, RoundedRectangle, Uneven, Capsule, Ellipse, Circle
}

Now, define the structure to store the information about the shape:

struct ShapeDescription: Identifiable {
    let id = UUID()
    var type: Shapes
    var name: String
}

In the view, create an array containing all the shape information:

struct ContentView: View {
    var shapes: Array<ShapeDescription> = 
[ShapeDescription(type: .Rectangle, name: "Rectangle"), ShapeDescription(type: .RoundedRectangle, name: "Rounded Rectangle"), ShapeDescription(type: .Uneven, name: "Uneven Rounded Rectangle"), ShapeDescription(type: .Capsule, name: "Capsule"),
ShapeDescription(type: .Ellipse, name: "Ellipse"),
ShapeDescription(type: .Circle, name: "Circle")]

To display all the information, use a list:

var body: some View {
        NavigationStack {
            List(shapes) { shape in
                HStack {
                    Text(shape.name)
                }
            }
        }
    }
}

Currently, we only display the name. To show the shapes, one basic approach could be to add a switch statement based on the shape type within the list, but this would be inefficient. Instead, let’s create a function that returns a shape corresponding to its type:

    NavigationStack {
            List(shapes) { shape in
                HStack {
                    getShape(shape: shape.type)
                    Text(shape.name)
                }
            }
        }

Take a look at the getShape function:

@ViewBuilder
func getShape(shape: Shapes) -> some View {
    switch(shape) {
    case .Rectangle:
        Rectangle()
        .fill(.gray)
        .frame(width: 40, height: 40)
    case .RoundedRectangle:
    RoundedRectangle(cornerRadius: 10)
        .fill(.red)
        .frame(width: 40, height: 40)
    case .Uneven:
    UnevenRoundedRectangle(cornerRadii: .init(topLeading: 50, topTrailing: 50))
        .fill(.orange)
        .frame(width: 40, height: 40)
    case .Capsule:
    Capsule()
        .fill(.green)
        .frame(width: 40, height: 20)
    case .Ellipse:
    Ellipse()
        .fill(.blue)
        .frame(width: 40, height: 20)
    case .Circle:
    Circle()
        .fill(.purple)
        .frame(width: 40, height: 40)

    }
}

Use the @ViewBuilder annotation to specify that the function ‘builds’ a view, in fact returning some View. This function simply contains a switch statement to return the appropriate shape.

That’s all. Enjoy and stay tuned.

Leave a Comment