Logo for tanaschita.com

Understanding composition in SwiftUI

Understand how SwiftUI builds and represents view hierarchies.

08 Jun 2026 · 7 min read

When working with SwiftUI, we constantly compose views from other views.

A Text view can be placed inside a VStack, a VStack can be embedded in a NavigationStack, and an entire screen can become a child of another view. This ability to build complex user interfaces from smaller building blocks is one of the core ideas behind SwiftUI.

Let's take a closer look at why this works.

Sponsorship logo
Architecture & Design Patterns for iOS
This book is a practical guide to essential architectural principles and design patterns for iOS development. It covers strategies for building maintainable apps with Swift and SwiftUI, including dependency injection, navigation, common patterns, and modularization.
LEARN MORE

Everything is a view

Whether we create a simple Text view:

Text("Hello World")

or a more complex container:

VStack {
Text("Title")
Text("Subtitle")
}

both conform to the View protocol. This means SwiftUI can treat simple and complex views in exactly the same way. From the framework's perspective, both examples are simply views.

The same principle applies to custom views. Everything participates in the hierarchy through the same abstraction.

One thing that makes this work in practice is @ViewBuilder, a result builder that allows us to write multiple child views inside a closure without explicit return statements. It's what makes the familiar SwiftUI syntax possible and what allows containers like VStack to accept multiple views as children.

A tree of views

One way to think about a SwiftUI interface is as a tree.

For example:

NavigationStack {
VStack {
Text("Title")
HStack {
Text("Author")
Spacer()
Text("Date")
}
}
}

creates a hierarchy similar to:

NavigationStack
└── VStack
├── Text
└── HStack
├── Text
├── Spacer
└── Text

Each node in the tree is itself a view. Some views are simple leaf nodes such as Text or Spacer. Others act as containers that group multiple child views together.

Because they all conform to the same protocol, SwiftUI can traverse and update the hierarchy in a uniform way.

The Composite pattern

This idea is closely related to the Composite pattern in software design, a design pattern where individual objects and groups of objects are treated through the same abstraction.

In SwiftUI:

As a result, complex user interfaces can be built from smaller pieces without requiring different APIs for individual views and groups of views.

Why composition matters

Composition is one of the main reasons SwiftUI scales so well. To see why, consider a screen built as a single large view:

struct ArticleListView: View {
let articles: [Article]
var body: some View {
List(articles) { article in
VStack(alignment: .leading) {
Text(article.title)
.font(.headline)
Text(article.author)
.font(.subheadline)
Text(article.date)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
}

As the row grows more complex, the entire screen becomes harder to read and the row layout becomes impossible to reuse elsewhere. Extracting it into its own component solves both problems:

struct ArticleRowView: View {
let article: Article
var body: some View {
VStack(alignment: .leading) {
Text(article.title)
.font(.headline)
Text(article.author)
.font(.subheadline)
Text(article.date)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
struct ArticleListView: View {
let articles: [Article]
var body: some View {
List(articles) { article in
ArticleRowView(article: article)
}
}
}

Each component now has a single responsibility, and ArticleRowView can be reused in a search results screen, a featured section, or anywhere else that needs to display an article.

This is composition in practice: breaking interfaces into small focused views and combining them into larger structures.

Why SwiftUI doesn't just store child views

At this point, we might wonder why SwiftUI uses concepts such as View, some View, and @ViewBuilder instead of a simpler approach like:

protocol View {
var children: [any View] { get }
}

Rather than storing view hierarchies as collections of child views, SwiftUI uses Swift's type system to represent much of the hierarchy at compile time. For example, when we write:

VStack {
Text("Title")
Text("Subtitle")
Image(...)
}

the @ViewBuilder transforms the child views into a concrete type that becomes part of the view hierarchy itself. Multiple child views are represented as tuple-based view content, simplified such as:

TupleView<(Text, Text, Image)>

Unlike an array of any View, the resulting type still contains information about the exact child views that make up the hierarchy. With this type information available, the framework can reason about the structure of the hierarchy more efficiently and preserve type safety throughout the view tree.

This also helps explain why some View fits SwiftUI so well. Each view's body produces a specific concrete type, but that type can quickly become long and implementation-specific. By returning some View, we tell the compiler that the body returns one specific view type, without having to name that type explicitly.

This tuple-based design also had an interesting historical consequence. Early SwiftUI versions only supported up to ten direct child views inside a @ViewBuilder closure, because each possible child count required its own explicit implementation. If we needed more, we had to split the children into another container, such as Group:

VStack {
Text("1")
Text("2")
// ...
Group {
Text("11")
Text("12")
}
}

As Swift evolved, in part through variadic generics introduced in Swift 5.9, this limitation was lifted. Modern SwiftUI can represent arbitrarily large groups of child views without requiring Group as a workaround.

Conclusion

Composition is one of the foundational ideas behind SwiftUI.

By treating simple views, custom components, and entire view hierarchies through the same View abstraction, SwiftUI makes it easy to build complex interfaces from small reusable building blocks.

Under the hood, the framework goes even further by representing much of the hierarchy through Swift's type system. While most of these details remain invisible in day-to-day development, understanding them helps explain why SwiftUI's compositional model feels both flexible and powerful.

Sponsorship logo
Preparing for a technical iOS job interview
Preparing for a technical iOS Job Interview with over 300 questions & answers. Covering Swift & Objective-C, SwiftUI & UIKit, Combine, HTTP Networking, iOS File System, Core Data, Concurrency with async/await, Security, Automated Testing, Dependency Management, AI & Machine Learning and more.
LEARN MORE
Sponsorship logo
Architecture & Design Patterns for iOS
This book is a practical guide to essential architectural principles and design patterns for iOS development. It covers strategies for building maintainable apps with Swift and SwiftUI, including dependency injection, navigation, common patterns, and modularization.
LEARN MORE
Sponsorship logo
Become a sponsor of tanaschita.com
By publishing an article on different iOS topics every week, tanaschita.com is constantly growing in the developer community and may provide a great audience for you as a sponsor.
CLICK TO LEARN MORE

Newsletter

Image of a reading marmot
Subscribe

Like to support my work?

Say hi

Related tags

Articles with related topics

book

architecture

design patterns

swift

swiftui

ios

My new book on architecture & design patterns for iOS

I finally finished my new book on building scalable and maintainable apps with Swift and SwiftUI.

19 Aug 2025 · 2 min read

Latest articles and tips

© 2026 tanaschita.com

Privacy policy

Impressum