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
@ViewBuilderwith functions that return a concrete view type (onlysome 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?
2. What must you call on an Image loaded from an asset catalog to enable flexible sizing?
3. Which view combines an icon and text in a standardized layout?
4. What is the role parameter on Button used for?
5. Why should you extract subviews into separate structs?
Flashcards
Question
What does @ViewBuilder do?
Click to reveal answer
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?
Click to reveal answer
Answer
VStack (vertical), HStack (horizontal), ZStack (layered/overlapping).
Question
What must you call on an asset Image before resizing?
Click to reveal answer
Answer
.resizable() -- without it, images render at natural pixel size.
Question
What is the difference between Button and Label?
Click to reveal answer
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?
Click to reveal answer
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.