Your first View
What some View actually means, why body is recomputed rather than mutated, and the mental shift from UIKit.
By the end you will be able to
- Write a View conforming type and explain every part of its declaration
- Explain what "declarative" means in terms of what you write and what the framework does
- Compose views from smaller views and pass data in
In SwiftUI, when does the code inside var body: some View run?
The smallest possible view
struct Greeting: View {
var body: some View {
Text("Hello, world!")
}
}
Four things, each worth naming:
struct— views are values, not objects. They are created and thrown away constantly, which is only cheap because they are structs.: View— a protocol with one requirement:body.var body: some View— a computed property returning some specificViewtype the caller does not need to know.Text("Hello, world!")— itself aView. Everything in SwiftUI is a view.
Try it: change the text, swap .title for .largeTitle or .caption, change the colour to .orange. The preview updates as soon as you press Run.
What some View means
some View is an opaque return type. It says: this returns one specific type conforming to View, chosen at compile time, and I am not telling you which.
Why not just write View? The compiler refuses that outright (View has associated types), but the more useful question is what the alternative world would cost: any View erases the concrete type into a box, and a hierarchy of boxes is one SwiftUI can no longer see into — yet the tree of concrete types is exactly what it diffs cheaply ("still a VStack of Text over Image") and what the compiler optimises. some View splits the difference: the concrete type stays visible to the machinery, hidden only from you — so you can change the body without ever changing the signature.
The actual type piles up as you add modifiers. Add .padding() to a Text and the concrete type is already:
ModifiedContent<Text, _PaddingLayout>
— and each further modifier wraps another ModifiedContent layer around that. (A handful of Text-specific modifiers like .font are special-cased to return Text itself, but the general-purpose ones all wrap.) Nobody wants to write these types. some View is how you avoid it.
Declarative versus imperative
In UIKit you write instructions: create a label, set its font, add it to a subview, and later, when something changes, remember to update it. Every state change needs matching update code, and forgetting one is the classic UIKit bug.
In SwiftUI you write a function from state to appearance. When the state changes, SwiftUI re-runs the function. There is no update code to forget.
let label = UILabel()
label.text = "Hello"
label.font = .preferredFont(
forTextStyle: .title1)
label.textColor = .systemBlue
view.addSubview(label)
label.translatesAutoresizing... = false
NSLayoutConstraint.activate([...])
// Later, when the name changes:
label.text = "Hello, \(name)"
// (if you remember)struct Greeting: View {
let name: String
var body: some View {
Text("Hello, \(name)")
.font(.title)
.foregroundStyle(.blue)
}
}
// Later, when the name changes:
// nothing — the view is recomputed.The right-hand version has no update path to forget, because there is no update path. That is the whole trade: you give up direct control over when things happen and get correctness-by-construction in return.
Composing views
Views nest. A view's body can contain other views, including ones you wrote:
Badge is a view used inside ProfileRow, which is used inside the preview. Data flows down through initializers — Badge(text: role) — which is exactly the memberwise initializer you already know from structs.
#Preview and Xcode canvas
The #Preview macro declares a preview. In Xcode it renders live in the canvas beside your code and updates as you type. Here it renders in the phone frame beside the editor.
#Preview {
ProfileRow(name: "Ada", role: "Author")
}
#Preview("Dark mode") {
ProfileRow(name: "Ada", role: "Author")
.preferredColorScheme(.dark)
}
Multiple previews with names let you check several states side by side, which is the fastest way to test edge cases — long names, empty strings, extreme dynamic type.
Views are values, and modifiers return new views. What is the type relationship here?
struct Simple: View {
var body: some View {
Text("A")
}
}
struct Wrapped: View {
var body: some View {
Text("B").padding()
}
}
#Preview {
VStack {
Simple()
Wrapped()
}
}Why are SwiftUI views structs rather than classes?
Build a stat tile
Build a StatTile view showing a large value above a small caption, centred, with padding and a grey background with rounded corners. The preview must show two of them side by side.
Compose two custom views
Make a Card view containing a title and a Badge, then show two cards stacked vertically.
Describe, in your own words, one bug that is possible in UIKit and structurally impossible in SwiftUI. Then describe one thing UIKit lets you do that SwiftUI makes harder.
You can now:
- Write a
Viewand explain every part of the declaration - Say what
some Viewbuys you overView - Explain why
bodymust be pure and how declarative differs from imperative - Compose custom views and pass data down through initializers
Next up: the layout system — how SwiftUI actually decides where things go, which is not what most people assume.