SwiftUI is declarative: instead of mutating view objects, you write a struct whose body describes the UI for the current state. Learn the View protocol, previews, and how views compose.
UIKit was imperative — you built view objects and mutated them. SwiftUI is declarative: a View is a struct with a body that describes what the UI should look like for the current data, and SwiftUI produces the new UI when the data changes. You stop micromanaging widgets and express the UI as a function of state — the same shift as Jetpack Compose on Android.
import SwiftUI
// a View is a struct with a 'body' that DESCRIBES the UI
struct GreetingView: View {
let name: String
var body: some View {
Text("Hello, \(name)!") // describes UI for the given data
}
}
// old (imperative): label.text = "Hello"
// SwiftUI (declarative): the UI is a function of 'name'. Change it -> SwiftUI
// re-renders GreetingView. No manual mutation.Xcode renders any view live in the canvas via a preview, without building and launching the whole app — a fast feedback loop for building UI. You can preview a view in multiple states, sizes, and light/dark mode at once. You will add previews to almost every view you write to iterate quickly.
struct GreetingView: View {
let name: String
var body: some View { Text("Hello, \(name)!") }
}
// render live in Xcode's canvas without launching the app:
#Preview {
GreetingView(name: "Ada")
}
#Preview("Dark mode") {
GreetingView(name: "Ada")
.preferredColorScheme(.dark)
}
// previews give a build-see-adjust loop — preview components in isolation.Because views are ordinary structs, you build UIs by composing them — small views called from larger ones, data passed as properties, and plain Swift control flow (if, ForEach) to include or repeat UI. This composition is the essence of SwiftUI: reusable pieces assembled into screens, with the full power of Swift available inside your UI code.
struct Badge: View {
let text: String
var body: some View {
Text(text).padding(6).background(.blue).foregroundStyle(.white)
}
}
struct ProfileView: View {
let name: String
let isOnline: Bool
var body: some View {
VStack {
Text(name).font(.title)
if isOnline { // plain Swift control flow in the UI
Badge(text: "online") // compose a smaller view
}
}
}
}A body returns some View — an opaque type meaning "a specific concrete view I don’t need to spell out." SwiftUI assembles these into a lightweight view tree that it diffs and re-renders efficiently. Views are cheap value types created and destroyed freely, so you keep them small and push real state into dedicated state holders (next lessons).
var body: some View { ... }
'some View' = an opaque return type: "one specific View type, hidden".
It lets you return a complex nested view without writing its exact type.
Key mental model:
- Views are lightweight VALUE TYPES (structs), created + destroyed freely
- SwiftUI builds a view TREE, diffs it on change, and updates only what moved
- so: keep views small + cheap; don't store heavy state in the view itself
-> use @State / observable models (next lessons)
"UI = f(state), re-render on change" — the whole model.