- iOS 18 / Swift 6:
@Observablereplaces@ObservableObject,NavigationStackreplacesNavigationView - Layout:
VStack/HStack/ZStackfor basic,Grid+GridRowfor complex - State flows down via
@State→@Binding; shared state via@Observableclass +@Environment SwiftData+@Modelreplaces most CoreData patterns- All SwiftUI views are value types — mutations must go through state
Quick reference tables
Text & display
| View / Modifier | Example | Notes |
|---|---|---|
Text | Text("Hello") | Basic text |
| Font | .font(.title) | .largeTitle, .title, .title2, .headline, .body, .caption |
| Weight | .fontWeight(.bold) | .ultraLight → .black |
| Color | .foregroundStyle(.blue) | Use .foregroundStyle not .foregroundColor (deprecated iOS 17+) |
| Multiline | .lineLimit(3) | nil = unlimited |
| Truncation | .truncationMode(.tail) | .head, .middle, .tail |
| Markdown | Text("**Bold** _italic_") | Supported natively |
Label | Label("Settings", systemImage: "gear") | Icon + text pair |
Image | Image(systemName: "star.fill") | SF Symbols |
AsyncImage | AsyncImage(url: url) | Remote image with built-in loading |
Layout containers
| Container | Use for |
|---|---|
VStack(alignment: .leading, spacing: 8) | Vertical stack |
HStack(alignment: .center, spacing: 12) | Horizontal stack |
ZStack(alignment: .bottomTrailing) | Overlapping views |
LazyVStack / LazyHStack | Large lists (deferred rendering) |
Grid + GridRow | Table-like 2D layout |
ViewThatFits | Picks first view that fits in available space |
ScrollView | Scrollable container (vertical by default) |
ScrollView(.horizontal) | Horizontal scroll |
List | Rows with built-in separators and swipe actions |
Form | Settings-style grouped rows |
GroupBox | Grouped content with a border |
Spacer() | Flexible space that fills available room |
Divider() | Horizontal separator line |
Common modifiers
| Modifier | Example |
|---|---|
| Frame | .frame(width: 100, height: 50) |
| Max frame | .frame(maxWidth: .infinity) |
| Padding | .padding() / .padding(.horizontal, 16) |
| Background | .background(.ultraThinMaterial) |
| Corner radius | .clipShape(RoundedRectangle(cornerRadius: 12)) |
| Shadow | .shadow(color: .black.opacity(0.1), radius: 8) |
| Overlay | .overlay(alignment: .topTrailing) { badge } |
| Opacity | .opacity(0.5) |
| Scale | .scaleEffect(1.1) |
| Rotation | .rotationEffect(.degrees(45)) |
| Hidden | .hidden() / .opacity(0) (hidden still takes space) |
| Disabled | .disabled(true) |
| Tint | .tint(.indigo) |
| Bold shorthand | .bold() |
State management
The state hierarchy
@State (local, private)
↓ passed down as
@Binding (two-way reference to parent state)
@Observable class (shared across view tree)
↓ injected via
@Environment (read anywhere below injection point) @State — local view state
struct CounterView: View {
@State private var count = 0
var body: some View {
Button("Count: \(count)") {
count += 1
}
}
} @Binding — pass state to a child
struct ToggleView: View {
@Binding var isOn: Bool // receives reference from parent
var body: some View {
Toggle("Enable", isOn: $isOn)
}
}
// Parent passes binding with $
ToggleView(isOn: $featureEnabled) @Observable — shared state (iOS 17+, recommended)
Replaces the old @ObservableObject / @Published pattern:
import Observation
@Observable
class UserStore {
var name = "Alice"
var isLoggedIn = false
}
struct ProfileView: View {
var store: UserStore // no property wrapper needed
var body: some View {
Text(store.name)
Button("Logout") { store.isLoggedIn = false }
}
} @Observable (Swift 5.9+, iOS 17+) is the modern API. It tracks only the specific properties each view reads — no @Published needed. Use @ObservableObject only when supporting iOS 16 or below.
@Environment — inject a shared object
// At app or scene root
ContentView()
.environment(UserStore())
// Anywhere in the view tree
struct SettingsView: View {
@Environment(UserStore.self) var store
var body: some View {
Text("Hello, \(store.name)")
}
} @AppStorage — persist to UserDefaults
struct SettingsView: View {
@AppStorage("darkMode") var darkMode = false
var body: some View {
Toggle("Dark Mode", isOn: $darkMode)
}
} Navigation
NavigationStack (iOS 16+)
struct ContentView: View {
var body: some View {
NavigationStack {
List(items) { item in
NavigationLink(item.title, value: item)
}
.navigationTitle("Items")
.navigationDestination(for: Item.self) { item in
ItemDetailView(item: item)
}
}
}
} Programmatic navigation with a path
@State private var path = NavigationPath()
NavigationStack(path: $path) {
ContentView()
.navigationDestination(for: Item.self) { item in
DetailView(item: item)
}
}
// Navigate programmatically:
path.append(selectedItem)
// Go back to root:
path.removeLast(path.count) TabView
TabView {
HomeView()
.tabItem { Label("Home", systemImage: "house") }
SearchView()
.tabItem { Label("Search", systemImage: "magnifyingglass") }
ProfileView()
.tabItem { Label("Profile", systemImage: "person") }
} Sheet and full-screen cover
@State private var showSheet = false
Button("Open") { showSheet = true }
.sheet(isPresented: $showSheet) {
SheetContentView()
.presentationDetents([.medium, .large]) // Bottom sheet heights
}
// Full screen:
.fullScreenCover(isPresented: $showFullScreen) {
FullScreenView()
} Lists and data
Basic List
List(fruits, id: \.self) { fruit in
Text(fruit)
}
// Section headers
List {
Section("Favorites") {
ForEach(favorites) { item in
ItemRow(item: item)
}
}
} Swipe actions
List(items) { item in
ItemRow(item: item)
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
delete(item)
} label: {
Label("Delete", systemImage: "trash")
}
}
.swipeActions(edge: .leading) {
Button { pin(item) } label: {
Label("Pin", systemImage: "pin")
}
.tint(.yellow)
}
} @Query with SwiftData
import SwiftData
@Model
class Task {
var title: String
var isDone: Bool
init(title: String) {
self.title = title
self.isDone = false
}
}
struct TaskListView: View {
@Query(sort: \Task.title) var tasks: [Task]
@Environment(\.modelContext) var context
var body: some View {
List(tasks) { task in
Text(task.title)
}
Button("Add Task") {
context.insert(Task(title: "New Task"))
}
}
} Animations
Implicit animation
@State private var expanded = false
RoundedRectangle(cornerRadius: 12)
.frame(height: expanded ? 200 : 80)
.animation(.spring(duration: 0.4), value: expanded)
.onTapGesture { expanded.toggle() } withAnimation block
Button("Animate") {
withAnimation(.easeInOut(duration: 0.3)) {
isVisible.toggle()
scale = isVisible ? 1.0 : 0.5
}
} Common animation curves
| Modifier | Behavior |
|---|---|
.linear(duration:) | Constant speed |
.easeIn(duration:) | Starts slow, ends fast |
.easeOut(duration:) | Starts fast, ends slow |
.easeInOut(duration:) | Slow start and end |
.spring(duration:bounce:) | Bouncy (iOS 17+ API) |
.interactiveSpring() | Tracks user gesture |
Transitions (view appear/disappear)
if isVisible {
CardView()
.transition(.asymmetric(
insertion: .slide,
removal: .opacity
))
} Common patterns
Conditional view
// Use if/else inside ViewBuilder
VStack {
if isLoading {
ProgressView()
} else {
ContentView()
}
} Safe area and keyboard avoidance
.safeAreaInset(edge: .bottom) {
ComposerBar() // Floats above tab bar / keyboard
}
.scrollDismissesKeyboard(.interactively) task modifier (async on appear)
.task {
await viewModel.loadData()
}
// With ID — re-runs when ID changes:
.task(id: selectedTab) {
await viewModel.reload(for: selectedTab)
} UIKit migration table
| UIKit | SwiftUI equivalent |
|---|---|
UILabel | Text |
UIButton | Button |
UIImageView | Image / AsyncImage |
UITextField | TextField |
UITextView | TextEditor |
UITableView | List |
UICollectionView | LazyVGrid / LazyHGrid |
UIScrollView | ScrollView |
UINavigationController | NavigationStack |
UITabBarController | TabView |
UIAlertController | .alert() modifier |
UIActivityIndicatorView | ProgressView() |
UISlider | Slider |
UISwitch | Toggle |
UIDatePicker | DatePicker |
UISegmentedControl | Picker(.segmented) |
viewDidAppear | .onAppear { } |
viewDidDisappear | .onDisappear { } |
@IBOutlet / @IBAction | State + binding |
NSFetchedResultsController | @Query (SwiftData) |
Summary
@Statefor local mutations,@Bindingto pass down,@Observablefor shared stateNavigationStack+.navigationDestinationreplaces all navigation patterns from before iOS 16SwiftData+@Queryis the modern data layer — no boilerplate, no NSManagedObjectContext passed manually.task(id:)handles async work correctly, cancelling and re-running when the ID changes- SwiftUI views are value types — think “what state produces this output” not “when do I call reload”
FAQ
Can I still use @ObservableObject in iOS 18?
Yes, it still compiles and works. But @Observable is faster (fine-grained tracking), requires less code, and is what Apple recommends for new projects targeting iOS 17+.
How do I call UIKit code from SwiftUI?
Use UIViewRepresentable (for UIView subclasses) or UIViewControllerRepresentable (for UIViewController). Apple’s own MapKit and AVKit views use this pattern internally.
Is SwiftUI ready for production apps?
Yes. As of iOS 17/18, all major gaps are closed. Apps like Keeword, Lasso, and many App Store apps ship 100% SwiftUI. The one remaining caveat is complex text layout — use UIViewRepresentable wrapping UITextView for rich text editors.
What is the difference between .sheet and .fullScreenCover?
.sheet presents a card that partially covers the screen and can be dismissed by dragging down. .fullScreenCover takes the full screen and cannot be dragged to dismiss — you must provide a dismiss button.
How does ViewThatFits work?
It tries each child view in order and displays the first one that fits in the available space without clipping. Useful for adaptive layouts that differ between iPhone and iPad.
What to read next
- React Hooks Cheat Sheet — state patterns on web mirror SwiftUI’s model
- Figma Shortcuts Cheat Sheet — design the UI before you build it
- TypeScript Cheat Sheet — if you also work on cross-platform with React Native
Related Articles
Deepen your understanding with these curated continuations.
Ollama Cheat Sheet: Local LLMs, Models, API & Integration (2026)
Complete Ollama reference — pull and run local LLMs, API endpoints, Python/JS integration, multimodal models, model management, and GPU setup in 2026.
Homebrew Cheat Sheet: Every Command macOS Devs Need
Complete Homebrew reference for macOS and Linux — install, update, cask, services, Brewfile, troubleshooting, and pinning versions with working commands.
mise Cheat Sheet: Unified Runtime & Tool Manager (2026)
Complete mise reference — install and switch Node, Python, Rust, Go versions, manage tools, activate via shell, Docker, and CI/CD with working commands.