SwiftUI is Apple's framework for building user interfaces on iPhone, iPad, Mac, Apple Watch, Apple TV and Vision Pro. Instead of creating buttons and labels and then changing them step by step, you describe what the screen should look like for the current data, and SwiftUI works out how to draw it and keep it up to date.
Everything you've learned in this course comes together here. A SwiftUI screen is a struct that conforms to the View protocol. Its body property returns some View, the opaque type from the generics lesson. Views are composed from smaller views using closures, and customised with method calls called modifiers.
This lesson covers the building blocks: text, images and buttons, laying them out with stacks, styling them with modifiers, and splitting a screen into small, reusable views. You'll need a Mac with Xcode to run SwiftUI; Xcode's preview canvas shows your interface live as you type.
A first view
import SwiftUI
struct WelcomeView: View {
var body: some View {
VStack {
Image(systemName: "sparkles")
.font(.largeTitle)
Text("Welcome to Codevra")
.font(.title)
.bold()
}
.padding()
}
}
#Preview {
WelcomeView()
}bodydescribes the view's content; SwiftUI calls it whenever the view needs drawing.Image(systemName:)uses SF Symbols, thousands of icons that ship with Apple's platforms.#Previewshows the view in Xcode's canvas without running the app.
Stacks and spacing
| View | Arranges children |
|---|---|
VStack | Top to bottom |
HStack | Left to right (leading to trailing) |
ZStack | On top of each other, back to front |
Spacer() | Takes up all the spare space, pushing other views apart |
Stacks accept alignment: and spacing: arguments, such as VStack(alignment: .leading, spacing: 8). Stacks nest freely: a typical list row is an HStack containing an image and a VStack of two Texts.
Modifiers and their order
A modifier is a method that returns a new, changed view: .font(), .foregroundStyle(), .padding(), .background(), .frame(), .clipShape(). They chain, and order matters, because each wraps the result of the previous one:
Text("Sale")
.padding()
.background(.red) // red includes the padding
Text("Sale")
.background(.red) // red only behind the text
.padding()Modifiers applied to a stack, such as .font(.headline), flow down to the views inside it unless a child sets its own.
Small views and data
Split screens into small view structs. Data goes in through properties, just like any struct:
struct PriceTag: View {
let amount: Double
var body: some View {
Text(amount, format: .currency(code: "GBP"))
.padding(6)
.background(.yellow, in: .capsule)
}
}Use it as PriceTag(amount: 19.99). Because views are lightweight structs, creating many small ones costs almost nothing, and it keeps each body short and readable.
Text, Image and Label
Most screens are built from a handful of content views. These modifiers are the ones you'll reach for most often:
| View | Common modifiers | What they do |
|---|---|---|
Text("...") | .font(.headline), .foregroundStyle(.secondary) | Text style and colour |
Text("...") | .lineLimit(2), .multilineTextAlignment(.center) | Line count and alignment |
Image("photo") | .resizable(), .scaledToFit() | Let a picture grow or shrink to fit |
Image(systemName: "star") | .font(.title), .imageScale(.large) | Size an SF Symbol like text |
Label("Settings", systemImage: "gear") | .labelStyle(.iconOnly) | An icon and a title together |
A photo from your asset catalogue keeps its original size until you add .resizable(), which is why a large photo can spill off the screen. Follow it with .scaledToFit() to keep its proportions. SF Symbols behave like text instead, so they follow the font size.
Showing views conditionally
body can use ordinary if and switch to decide what to show:
var body: some View {
VStack {
if items.isEmpty {
Text("Nothing here yet")
} else {
Text("\(items.count) items")
}
}
}This works because body is built with a view builder, which turns each branch into a view and combines them into one type. That's also why body can't contain arbitrary statements, such as a for loop or a plain print. Repeat views with ForEach, which you'll meet in the lists lesson.
SwiftUI Syntax
SwiftUI building blocks
struct MyView: View {
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Title").font(.headline)
HStack {
Image(systemName: "star.fill").foregroundStyle(.yellow)
Spacer()
Button("Tap me") { print("Tapped") }
}
}
.padding()
.frame(maxWidth: .infinity)
}
}
#Preview { MyView() }SwiftUI Summary
- A SwiftUI view is a struct conforming to View, with a body that returns some View.
- Text, Image and Button are basic views; SF Symbols provide icons.
- VStack, HStack and ZStack arrange views; Spacer pushes them apart.
- Modifiers return new views, and their order changes the result.
- Split screens into small view structs, passing data in through properties.
SwiftUI Examples
Each example shows the code, the output it produces and what each part does.
A product card
import SwiftUI
struct ProductCard: View {
let name: String
let price: Double
let symbol: String
var body: some View {
HStack(spacing: 12) {
Image(systemName: symbol)
.font(.title)
.frame(width: 48, height: 48)
.background(.blue.opacity(0.15), in: .rect(cornerRadius: 10))
VStack(alignment: .leading) {
Text(name).font(.headline)
Text(price, format: .currency(code: "GBP"))
.foregroundStyle(.secondary)
}
Spacer()
Image(systemName: "chevron.right")
.foregroundStyle(.tertiary)
}
.padding()
}
}
#Preview {
ProductCard(name: "Desk lamp", price: 24.5, symbol: "lamp.desk")
}[Preview: a row with a lamp icon in a light blue rounded square, "Desk lamp" in bold above "£24.50" in grey, and a chevron at the right edge]Line-by-line explanation
- Lines 4 to 6Data comes in through properties, so the same card works for any product.
- Lines 10 to 13The icon gets a fixed square frame and a rounded tinted background.
- Lines 14 to 18A leading-aligned VStack puts the name above the price.
- Line 16The currency format adds the pound sign and two decimal places.
- Line 19Spacer pushes the chevron to the far right.
Modifier order
import SwiftUI
struct BadgeDemo: View {
var body: some View {
VStack(spacing: 20) {
Text("New")
.padding(10)
.background(.green)
.clipShape(.capsule)
Text("New")
.background(.green)
.padding(10)
Text("New")
.padding(10)
.background(.orange)
.padding(4)
.background(.purple)
}
}
}
#Preview { BadgeDemo() }[Preview: a green capsule around "New"; then "New" with green only behind the letters; then "New" in an orange box with a thin purple border]Line-by-line explanation
- Lines 6 to 9Padding first, then background, gives a filled badge; clipShape rounds it.
- Lines 11 to 13Background first only colours the text's own size; the padding comes after, outside it.
- Lines 15 to 19Alternating padding and backgrounds layers colours, like a border.
A button that changes text
import SwiftUI
struct GreetingView: View {
@State private var greeting = "Hello"
var body: some View {
VStack(spacing: 16) {
Text(greeting)
.font(.largeTitle)
Button("Change language") {
greeting = greeting == "Hello" ? "Hola" : "Hello"
}
.buttonStyle(.borderedProminent)
}
}
}
#Preview { GreetingView() }[Preview: "Hello" in large type above a filled blue "Change language" button; tapping it switches the text to "Hola" and back]Line-by-line explanation
- Line 4@State stores a value the view owns; the next lesson covers it in depth.
- Lines 10 to 12A Button has a title and an action closure that runs when tapped.
- Line 11Changing state makes SwiftUI call body again and update the Text.
- Line 13buttonStyle picks one of the system button looks.
Common SwiftUI Mistakes
Expecting views to change when you edit a plain property
struct Counter: View {
var count = 0
var body: some View {
Button("\(count)") { count += 1 } // error: 'self' is immutable
}
}struct Counter: View {
@State private var count = 0
var body: some View {
Button("\(count)") { count += 1 }
}
}Fixed font sizes
Text("Order summary").font(.system(size: 17)) // ignores the user's text size settingText("Order summary").font(.headline)One enormous body
var body: some View {
VStack { /* 150 lines of header, list, footer and buttons */ }
}var body: some View {
VStack {
HeaderView()
OrderList(orders: orders)
CheckoutBar(total: total)
}
}SwiftUI in Real Projects
SwiftUI in the industry
SwiftUI is now the default for new Apple platform projects, and it's the only way to build some features, such as widgets and many watchOS and visionOS apps. Teams build design systems from small reusable views like PriceTag and PrimaryButton, and use previews with sample data to review every state of a screen without running the app.
SwiftUI FAQ
What does some View mean in SwiftUI?
some View says that body returns one specific type that conforms to View, without making you write that type out. The real type is a long nested combination of every stack and modifier you used, and the compiler works it out for you.
Why are SwiftUI views structs and not classes?
Views are lightweight descriptions of what should appear, not the on-screen objects themselves. SwiftUI recreates these structs often and compares them to work out what changed, which is cheap for value types. Data that must persist lives in state, not in the view struct.
Does the order of SwiftUI modifiers matter?
Yes. Each modifier wraps the view produced by the one before it. .padding().background(.red) colours the padded area, while .background(.red).padding() colours only behind the content and then adds clear space around it.
Can I use SwiftUI without a Mac?
No. SwiftUI is part of Apple's platforms, so you need Xcode on a Mac, or Swift Playgrounds on an iPad or Mac, to build and preview SwiftUI views. The Swift language itself runs on Linux and Windows, but SwiftUI doesn't.
What is the difference between VStack, HStack and ZStack?
VStack arranges its children vertically, HStack horizontally, and ZStack layers them on top of each other, with later views in front. Nest them to build most layouts, and use Spacer() to push views apart.
SwiftUI Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the view so it conforms to SwiftUI's View protocol.
struct HelloView: {
var body: some View {
Text("Hello")
}
}
Which stack arranges views from top to bottom?
What does Spacer() do inside an HStack?
Which of these gives a coloured box that includes space around the text?
Why does SwiftUI's body return some View rather than a named type?
SwiftUI Quiz
5 questions to check what you learned.
What kind of Swift type is usually used for a SwiftUI view?
- class
- struct
- enum
- actor
What does Image(systemName: "star.fill") show?
- An image from your assets folder
- A photo from the library
- An emoji
- An SF Symbols icon
Why does the order of modifiers matter?
- Each modifier wraps the view produced by the previous one
- It doesn't
- Only the first modifier is applied
- Modifiers are sorted alphabetically
What does #Preview do?
- Runs the app on a device
- Takes a screenshot
- Shows the view live in Xcode's canvas
- Publishes the app
How should text sizes usually be set in SwiftUI?
- With fixed point sizes
- In pixels
- With semantic fonts like .headline and .body, which scale with the user's settings
- They can't be changed