Arrange UI with VStack, HStack, and ZStack, and shape every view with modifiers — the chainable system controlling padding, size, color, and more. Plus Spacer and frame for flexible layouts.
Three stacks cover most layout: VStack arranges children vertically, HStack horizontally, and ZStack overlaps them front-to-back (for backgrounds and badges). You control spacing and alignment with their parameters. Nesting these three composes essentially any screen layout — the SwiftUI counterpart to Compose’s Column/Row/Box.
struct ProfileHeader: View {
var body: some View {
VStack(alignment: .leading, spacing: 8) { // vertical
Text("Ada Lovelace").font(.title)
HStack(spacing: 16) { // horizontal
Text("Following: 120")
Text("Followers: 8k")
}
}
}
// ZStack { } overlaps children (e.g. an avatar with a badge on top).
}A modifier is a method that returns a new, decorated view — padding, background, foregroundStyle, cornerRadius, and hundreds more — and you build up appearance by chaining them. Order matters: each modifier wraps the previous result, so padding before background differs from padding after it. Chaining modifiers is how all SwiftUI styling works.
struct Tag: View {
let text: String
var body: some View {
Text(text)
.padding(.horizontal, 12) // inner spacing
.padding(.vertical, 6)
.background(.blue, in: Capsule()) // shape + fill
.foregroundStyle(.white)
.onTapGesture { print("tapped") }
}
}
// ORDER MATTERS: .padding then .background = color includes the padding;
// .background then .padding = padding OUTSIDE the color. Each modifier wraps
// the previous view.Layout flexibility comes from a few tools: Spacer expands to push views apart, and frame sets size or alignment (with maxWidth: .infinity to fill available width). Together they build responsive layouts — a title pushed left with actions on the right, a button stretched full width — without hardcoded pixel positions.
struct Toolbar: View {
var body: some View {
HStack {
Text("Title")
Spacer() // pushes the icons to the right
Image(systemName: "magnifyingglass")
Image(systemName: "ellipsis")
}
.padding()
}
}
struct WideButton: View {
var body: some View {
Button("Continue") { }
.frame(maxWidth: .infinity) // fill the available width
.padding()
}
}ZStack and the background modifier layer color, gradients, and shapes behind content, and SwiftUI keeps content inside the safe area (away from the notch and home indicator) by default — you opt out with ignoresSafeArea for full-bleed backgrounds. Understanding the safe area prevents content from hiding under system UI, a frequent beginner surprise.
struct HeroView: View {
var body: some View {
VStack { Text("Welcome").font(.largeTitle) }
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(
LinearGradient(colors: [.blue, .purple],
startPoint: .top, endPoint: .bottom)
)
.ignoresSafeArea() // let the gradient extend under the notch/home bar
}
}
// content stays in the SAFE AREA by default; ignoresSafeArea() opts out for
// full-bleed backgrounds. Keep interactive content inside the safe area.