SwiftUI @ViewBuilder and some View Explained
Why body returns 'some View', how @ViewBuilder turns multiple statements into one view tree, and what opaque types buy SwiftUI's diffing — the syntax behind every view you write.
import SwiftUI
struct InfoCard<Content: View>: View {
let title: String
@ViewBuilder var content: Content
var body: some View {
VStack(alignment: .leading, spacing: 8) {
Text(title).font(.headline)
content
}
.padding()
.background(.quaternary.opacity(0.4), in: .rect(cornerRadius: 14))
}
}
struct Demo: View {
@State private var detailed = false
var body: some View {
InfoCard(title: "Status") {
Label("Online", systemImage: "checkmark.circle")
if detailed {
Text("Latency 24 ms").font(.caption)
}
}
.padding()
}
}The type you never write
A modest screen's real type looks like VStack<TupleView<(Text, ModifiedContent<Image, _PaddingLayout>, …)>>. some View lets body promise "one concrete view type" without spelling it — the compiler still knows the exact type, which SwiftUI uses to diff updates structurally instead of re-creating trees.
What @ViewBuilder actually does
Closures and properties marked @ViewBuilder get their statements rewritten: several views become a TupleView, an if/else becomes _ConditionalContent, an if without else wraps in Optional. That is why you can 'just list views' inside a VStack — and why both branches of a condition can be different view types without erasure.
@ViewBuilder
private var statusBadge: some View {
if isOnline {
Label("Online", systemImage: "checkmark.circle")
} else {
Label("Offline", systemImage: "xmark.circle")
}
}
Why not AnyView?
AnyView erases type structure, so SwiftUI can no longer see that 'the same Text changed' versus 'a different view appeared' — transitions degrade and diffing coarsens. Builders preserve identity through branches. AnyView remains a legitimate tool for heterogeneous collections, but it is the last resort, not the fix for a builder you forgot.
Accepting view closures
Generic content parameters (<Content: View> plus @ViewBuilder var content: Content) are how VStack, GroupBox, and your own containers accept arbitrary children — the standard shape for reusable chrome.
Common mistakes
- AnyView sprinkled to fix 'branches return different types' that @ViewBuilder solves.
- Doing data work inside body instead of returning views.
- Giant builder expressions the compiler struggles to type-check — extract subviews.
Related reference
Create reusable control styles when repeated UI behavior belongs to the component system rather than a single screen.
Move repeated visual decisions into ViewModifier and Shape extensions so the UI reads like product language.
#Preview declares canvas previews with names, traits like landscape orientation and size fitting, plus @Previewable for stateful previews — fast iteration without running the app.