Skip to content

How to Extract Subviews in SwiftUI | SwiftUI Bootcamp #21

Building a list of identical-looking cards by copy-pasting the same view code three times works — until you need to change the corner radius on all of them. This lesson shows you how to extract a configurable subview so the same component renders everywhere from a single definition.

What You'll Learn

  • How to create a standalone SwiftUI struct that accepts parameters (like a mini-component)
  • How to use let properties to pass data into a subview at the call site
  • Why extracted subviews make your code more reusable and easier to change in one place

Mental Model

Think of a LEGO brick. A single brick design can appear hundreds of times in a model, each instance in a slightly different position or color, but they all share the same physical shape. Extracting a subview in SwiftUI is like defining that brick shape once — struct MyItem: View — and then stamping it wherever you need it with different values plugged in. If you decide to round the corners more, you change the brick definition and every instance updates instantly.

Before subviews, your only option was to copy-paste the same VStack { Text ... Text ... }.padding().background().cornerRadius() block every time. That's not a brick — that's carving the same shape by hand each time. One typo in one copy and that card looks different from the others.

Detailed Explanation

When you extract a subview, you create a new struct that conforms to View. This struct has let (or var) properties that act as its inputs. At the call site you initialize it like any other Swift struct: MyItem(title: "Apples", count: 1, color: .red). SwiftUI treats this identically to writing out the full layout inline — there is no extra overhead.

The difference between a computed property (lesson 20) and a separate struct (this lesson) is about inputs and reuse. Computed properties live inside a single parent struct and share its state implicitly. A separate struct is fully independent — it knows nothing about the parent unless you explicitly pass data into it. This independence is what makes it reusable across multiple screens.

Use let for data a subview displays but does not own or change. If a subview needs to change a value in the parent, that's when you reach for @Binding (covered in lesson 22). Keeping let properties as the default keeps subviews predictable — the same input always produces the same output.

Xcode has a built-in refactoring shortcut for this: right-click on any view in body, choose "Extract Subview", and Xcode creates the new struct and the call site simultaneously. It's a great way to speed up the mechanical part of extraction.

Code Structure

ExtractSubviewsBootcamp.swift contains the main screen struct, its preview, and the reusable MyItem subview. The contentLayer computed property calls MyItem three times with different arguments, demonstrating how one definition produces three visually distinct but structurally identical cards.

Complete Code

ExtractSubviewsBootcamp.swift

swift
import SwiftUI

struct ExtractSubviewsBootcamp: View {
    var body: some View {
        ZStack {
            Color(#colorLiteral(red: 0.2588235438, green: 0.7568627596, blue: 0.9686274529, alpha: 1)).edgesIgnoringSafeArea(.all) // color literal — tap it in Xcode to open the color picker
            
            
            contentLayer
        }
    }
    
    var contentLayer: some View {
        HStack {
            MyItem(title: "Apples", count: 1, color: .red)    // same struct, different data
            MyItem(title: "Oranges", count: 10, color: .orange)
            MyItem(title: "Bananas", count: 34, color: .yellow)
        }
    }
    
}

struct ExtractSubviewsBootcamp_Previews: PreviewProvider {
    static var previews: some View {
        ExtractSubviewsBootcamp()
    }
}

struct MyItem: View {
    
    let title: String // passed in at the call site; this view cannot change it
    let count: Int
    let color: Color
    
    var body: some View {
        VStack {
            Text("\(count)")  // interpolates the Int as a string
            Text(title)
        }
        .padding()
        .background(color)   // uses the passed-in color, not a hard-coded value
        .cornerRadius(10)
    }
}

Code Walkthrough

  1. Color(#colorLiteral(...)) background — The color literal is a compile-time color value. In Xcode it renders as a colored square, making it easy to identify visually without running the app.

  2. contentLayer computed property — Keeps body to a single line. The actual layout (three items in an HStack) lives in contentLayer, so body only needs to describe the layering of background and content.

  3. MyItem(title:count:color:) at the call site — Each initialization looks like a Swift struct initializer with labeled arguments. SwiftUI doesn't know or care that MyItem is a "component" — it's just a view that returns a VStack.

  4. struct MyItem: View declaration — Placing this outside (but in the same file as) the parent is a common convention for small helper views. Once the file gets large, move subviews to their own files.

  5. let properties on MyItem — Because these are let, the subview cannot mutate them. This enforces a one-way data flow: the parent controls the data, and MyItem only displays it.

  6. .background(color).cornerRadius(10) on the VStack — The modifiers apply to the entire VStack as a unit. Padding runs first, then background fills the padded area, then corners are rounded — order matters here.

Common Mistakes

Mistake: Using @State inside a subview for data that comes from the parent
If MyItem had @State var count: Int, it would create its own private copy and ignore the value passed in from the parent. Use let for display-only data and @Binding only when the child needs to write back to the parent.

Mistake: Creating a subview but passing so many parameters it becomes harder to read than inline code
If your initializer has seven arguments, consider whether the subview needs a view model or whether you're extracting at the wrong level. The rule of thumb: a subview should have a clear, single visual responsibility.

Mistake: Putting the subview struct inside the parent struct's body
You cannot declare a new struct inside a var body: some View. Swift does not allow nested type declarations inside computed properties. Place subview structs at the top level of the file or in their own file.

Key Takeaways

  • A standalone struct subview accepts let inputs and produces the same output for the same inputs — this makes it predictable and reusable across screens.
  • Extraction lets you change a component once and have every instance update automatically.
  • The line between "extract as a computed property" and "extract as a separate struct" is inputs: if the chunk needs data passed in, make it a struct.

Last updated: June 27, 2026

Released under the MIT License.