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
letproperties 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
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
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.contentLayercomputed property — Keepsbodyto a single line. The actual layout (three items in an HStack) lives incontentLayer, sobodyonly needs to describe the layering of background and content.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 thatMyItemis a "component" — it's just a view that returns aVStack.struct MyItem: Viewdeclaration — 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.letproperties onMyItem— Because these arelet, the subview cannot mutate them. This enforces a one-way data flow: the parent controls the data, andMyItemonly displays it..background(color).cornerRadius(10)on theVStack— The modifiers apply to the entireVStackas 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
structsubview acceptsletinputs 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