Skip to content
beginner Phase 2 · SwiftUI Fundamentals

Views & ViewBuilder

Build views using Text, Image, Button, stacks, and the @ViewBuilder result builder.

50m
3 problems
Topic Progress 0%

Built-in Views

Text View

Text is the most fundamental display view in SwiftUI. It renders a single line of read-only text by default and supports formatting through modifiers.

Text("Hello, World!")
    .font(.headline)
    .foregroundColor(.primary)

// String interpolation
let name = "Alice"
Text("Welcome, \(name)!")

// Multi-line
text("This is a long text that will wrap across multiple lines when it exceeds the available width.")
    .lineLimit(3)
    .multilineTextAlignment(.center)

Text supports concatenation, interpolation, and markdown-style formatting. You can embed other Text views inside a Text using string interpolation for inline styling:

Text("Bold ") + Text("and ").bold() + Text("italic").italic()

Image View

Image displays images from your asset catalog, system symbols, or programmatically created UIImage instances.

// Asset catalog image
Image("logo")
    .resizable()
    .aspectRatio(contentMode: .fit)
    .frame(width: 200, height: 100)

// SF Symbol
Image(systemName: "star.fill")
    .font(.largeTitle)
    .foregroundColor(.yellow)

// From UIImage
Image(uiImage: myUIImage)
    .clipShape(Circle())

Always call .resizable() on asset images to enable flexible sizing. Without it, the image renders at its natural pixel size.

Button

Button triggers an action when tapped. It accepts a label parameter for its visual representation.

// Simple text button
Button("Tap Me") {
    print("Button tapped")
}

// Custom label
Button(action: { counter += 1 }) {
    HStack {
        Image(systemName: "plus.circle.fill")
        Text("Increment")
    }
    .padding()
    .background(Color.blue)
    .foregroundColor(.white)
    .cornerRadius(10)
}

// Role-based buttons
Button("Delete", role: .destructive) { deleteItem() }
Button("Cancel", role: .cancel) { dismiss() }

The role parameter automatically applies platform-appropriate styling and behavior, such as confirmation prompts for destructive actions.

Label

Label combines an image and text in a standardized layout. It is the recommended way to create tappable items in toolbars, menus, and lists.

Label("Favorites", systemImage: "heart.fill")
Label("Settings", systemImage: "gearshape")
    .labelStyle(.titleAndIcon)

Label supports three styles: .titleOnly, .iconOnly, and .titleAndIcon (default). It automatically adapts to the current context, such as collapsing to icon-only in compact navigation bars.

Other Essential Views

View Purpose
Toggle On/off switch with label
Slider Value selection within a range
Stepper Increment/decrement a value
Picker Selection from a list of options
ProgressView Loading indicator or determinate progress
Spacer Flexible space that pushes views apart
Divider Horizontal or vertical separator
Group Transparent container for organizing views
Section Container for grouped content in lists
Spacer Takes up available space in a stack

@ViewBuilder

The Problem @ViewBuilder Solves

In SwiftUI, the body property must return a single view. But real UIs are composed of multiple views arranged together. Without special handling, you would need to manually wrap everything in a container:

// Without @ViewBuilder - verbose nesting
var body: some View {
    VStack {
        HStack {
            Text("Hello")
            Spacer()
            Text("World")
        }
    }
}

How @ViewBuilder Works

@ViewBuilder is a result builder (a Swift feature) that lets you write multiple view expressions as if they were separate statements. The builder automatically combines them into a single composite view.

// With @ViewBuilder - clean, flat syntax
var body: some View {
    Text("Hello")
    Spacer()
    Text("World")
}

SwiftUI applies @ViewBuilder to the body property automatically. You do not need to add the annotation yourself on body.

Using @ViewBuilder in Custom Functions

You can use @ViewBuilder on your own functions to create view-building helpers:

@ViewBuilder
func makeHeader(title: String, subtitle: String?) -> some View {
    Text(title)
        .font(.largeTitle)
    if let subtitle {
        Text(subtitle)
        .font(.subheadline)
        .foregroundColor(.secondary)
    }
}

// Usage
struct ContentView: View {
    var body: some View {
        makeHeader(title: "Welcome", subtitle: "Sign in to continue")
    }
}

The some View return type is an opaque return type. It tells the compiler the function returns a specific but unnamed view type, which SwiftUI's infrastructure handles.

Conditional Views

@ViewBuilder enables conditional view inclusion using Swift's if/else/switch:

@ViewBuilder
func content(for state: AppState) -> some View {
    switch state {
    case .loading:
        ProgressView("Loading...")
    case .loaded(let data):
        DataList(data: data)
    case .error(let message):
        ErrorView(message: message)
    }
}

SwiftUI handles transitions between branches automatically, applying the specified transition animation.

ForEach in ViewBuilder

@ViewBuilder works with ForEach to generate repeated views from collections:

var body: some View {
    VStack {
        ForEach(items, id: \.id) { item in
            Text(item.name)
        }
    }
}

ForEach is not a view itself -- it generates views within the context of a container. Each element must be uniquely identifiable.

ViewBuilder Limitations

  • You cannot use @ViewBuilder with functions that return a concrete view type (only some View)
  • The maximum number of subviews in a single ViewBuilder block is 10 (wrap larger groups in containers)
  • You cannot assign the result of a ViewBuilder to a variable directly inside the builder

Composing Views

The Composition Principle

SwiftUI encourages building complex UIs by composing small, focused views into larger ones. Each view should have a single responsibility and be independently reusable.

struct UserAvatar: View {
    let imageURL: URL
    let size: CGFloat
    
    var body: some View {
        AsyncImage(url: imageURL) { image in
            image
                .resizable()
                .aspectRatio(contentMode: .fill)
        } placeholder: {
            Image(systemName: "person.circle.fill")
                .resizable()
                .foregroundColor(.gray)
        }
        .frame(width: size, height: size)
        .clipShape(Circle())
    }
}

struct UserRow: View {
    let user: User
    
    var body: some View {
        HStack(spacing: 12) {
            UserAvatar(imageURL: user.avatarURL, size: 50)
            VStack(alignment: .leading) {
                Text(user.name).font(.headline)
                Text(user.email).font(.caption).foregroundColor(.secondary)
            }
            Spacer()
        }
    }
}

Notice how UserRow does not concern itself with avatar rendering details. It delegates that to UserAvatar. This separation makes both views independently testable and reusable.

Stacks for Layout

Stacks are the primary way to arrange views horizontally, vertically, or layered on top of each other:

VStack(alignment: .leading, spacing: 8) {
    Text("Title")
        .font(.title)
    Text("Subtitle")
        .font(.subheadline)
    HStack {
        Image(systemName: "heart")
        Text("42 likes")
    }
    .foregroundColor(.red)
}

VStack arranges children vertically, HStack horizontally, and ZStack layers them on top of each other (useful for overlays).

View Composition Patterns

Extracted Subviews: Break complex views into smaller named views:

struct ProductCard: View {
    let product: Product
    
    var body: some View {
        VStack(alignment: .leading) {
            ProductImage(url: product.imageURL)
            ProductInfo(name: product.name, price: product.price)
            AddToCartButton(product: product)
        }
        .padding()
        .background(Color(.systemBackground))
        .cornerRadius(12)
        .shadow(radius: 2)
    }
}

Conditional Composition: Show different views based on state:

var body: some View {
    Group {
        if isLoggedIn {
            DashboardView()
        } else {
            LoginView()
        }
    }
    .animation(.easeInOut, value: isLoggedIn)
}

View Builders as Factories: Create reusable view factories:

@ViewBuilder
func sectionHeader(_ title: String) -> some View {
    Text(title)
        .font(.title2)
        .fontWeight(.semibold)
        .padding(.horizontal)
        .padding(.top, 8)
}

var body: some View {
    List {
        sectionHeader("Account")
        Section { /* account settings */ }
        sectionHeader("Preferences")
        Section { /* preferences */ }
    }
}

Benefits of Composition

  • Reusability: Small views can be used across multiple screens
  • Testability: Individual components can be previewed and tested in isolation
  • Readability: Each view has a clear, focused purpose
  • Maintainability: Changes to one component do not ripple through the entire codebase
  • Previewability: Xcode Previews let you see each component in isolation

Quiz

1. What does @ViewBuilder enable in SwiftUI?

Question 1 options

2. What must you call on an Image loaded from an asset catalog to enable flexible sizing?

Question 2 options

3. Which view combines an icon and text in a standardized layout?

Question 3 options

4. What is the role parameter on Button used for?

Question 4 options

5. Why should you extract subviews into separate structs?

Question 5 options

Flashcards

Question

What does @ViewBuilder do?

Answer

It is a result builder that automatically combines multiple view expressions into a single composite view, enabling clean multi-expression view bodies.

Question

What are the three stack types in SwiftUI?

Answer

VStack (vertical), HStack (horizontal), ZStack (layered/overlapping).

Question

What must you call on an asset Image before resizing?

Answer

.resizable() -- without it, images render at natural pixel size.

Question

What is the difference between Button and Label?

Answer

Button triggers an action on tap and accepts an action closure. Label only displays an icon+text combination with no built-in tap action.

Question

What is `some View` in SwiftUI?

Answer

An opaque return type that tells the compiler the function returns a specific but unnamed view type.

Revision Notes

Key Takeaways

  • 1. Text, Image, Button, and Label are the core building blocks of SwiftUI UIs
  • 2. @ViewBuilder automatically combines multiple views into a single composite view
  • 3. Always call .resizable() on asset images before applying frame modifiers
  • 4. Compose complex views by extracting small, reusable subview structs
  • 5. Stacks (VStack, HStack, ZStack) are the primary layout containers

Interview Tips

  • Explain how @ViewBuilder uses Swift's result builder feature to flatten multi-expression view bodies
  • Know the difference between Button (has action) and Label (display only)
  • Be ready to discuss view composition patterns: extracted subviews, conditional views, and view builder factories
  • Understand why views should be small, focused, and independently previewable

Cheat Sheet

Text: display text with formatting and concatenation.
Image: use .resizable() for asset images, systemName for SF Symbols.
Button: action + label, use role for destructive/cancel.
Label: icon + text in a standardized layout.
@ViewBuilder: enables multiple expressions in a single view body.
Stacks: VStack (vertical), HStack (horizontal), ZStack (layered).
Compose by extracting small focused views into separate structs.