iOS with SwiftUI · SwiftUI Foundations: Views, Layout, Lists and State
Your First iPhone App with SwiftUI
Create a project in Xcode, understand the App and View structs, preview your UI live with #Preview, and run the app in the simulator or on your iPhone.
What you'll build
A "Hello" screen with a button that counts taps, running in the iOS Simulator and on your own iPhone. You'll create the project, read the code Xcode generates, use live previews and make your first change.
The idea
SwiftUI is Apple's declarative UI framework for iPhone, iPad, Mac, Apple Watch, Apple TV and Vision Pro. You describe what the UI should look like for the current state, and SwiftUI works out how to update the screen when that state changes.
The pieces of a new project:
| Piece | What it does |
|---|---|
@main struct MyApp: App |
the entry point; declares the app's scenes (windows) |
struct ContentView: View |
a view: a struct whose body returns other views |
@State |
state owned by a view; changing it re-renders the view |
#Preview |
renders the view live in Xcode's canvas as you type |
Modifiers (.font, .padding) |
return a new, modified view |
Views are lightweight structs, recreated often; SwiftUI keeps their state separately, so recreating a view doesn't lose @State.
The code
In Xcode choose File > New > Project > iOS > App, with SwiftUI as the interface:
import SwiftUI
@main
struct HelloApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
@State private var taps = 0
var body: some View {
VStack(spacing: 16) {
Text("Hello from my iPhone")
.font(.title2.bold())
Button(tapLabel(taps)) {
taps += 1
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}
func tapLabel(_ taps: Int) -> String {
"Tapped \(taps) \(taps == 1 ? "time" : "times")"
}
#Preview {
ContentView()
}
Press Cmd-R to run in the simulator. To run on your iPhone, connect it, turn on Developer Mode (Settings > Privacy & Security), choose it as the run destination and sign with your Apple ID under Signing & Capabilities.
Try the logic
Views should stay thin: the label is a plain function, so it runs and tests without a device:
func tapLabel(_ taps: Int) -> String {
"Tapped \(taps) \(taps == 1 ? "time" : "times")"
}
var taps = 0
print(tapLabel(taps))
taps += 1
print(tapLabel(taps))
taps += 2
print(tapLabel(taps))
Output
Tapped 0 times Tapped 1 time Tapped 3 times
Run this on your computer — see “Get Started” for setup.
Common mistakes
- Plain properties for changing values.
var taps = 0in a view can't change (views are immutable structs); use@State private var. - Making
@Statepublic or passing it in.@Stateis owned by the view that declares it; pass values down and use@Bindingfor two-way access. - Heavy work in
body.bodyruns often; never start network calls or sort large arrays there. - Ignoring previews. Previews with sample data catch layout problems in dark mode and large text sizes without running the app.
Key points
- A SwiftUI app is an
Appstruct with scenes; screens areViewstructs with abody. @Stateholds a view's own changing values; changing it re-renders the view.- Modifiers return new views and are applied in order.
- Use
#Previewfor fast iteration and keep logic in plain, testable functions.
Further reading: SwiftUI tutorials, SwiftUI documentation.
Practice questions
Why are SwiftUI views structs, and where does their state live?
Answer
Views are lightweight value types that describe the UI and are recreated whenever SwiftUI needs a new description. State declared with @State is stored by SwiftUI outside the struct, tied to the view's identity in the hierarchy, so it survives the struct being recreated.
What does the some keyword mean in var body: some View?
Answer
It's an opaque return type: body returns one specific concrete View type that the compiler knows, but callers only see that it conforms to View. This lets SwiftUI keep full type information for performance without you writing the long generic type.
Open in the app to mark lessons complete, take notes and track your progress.