MeshWorld India Logo MeshWorld.
Cheatsheet SwiftUI iOS Swift Mobile Apple 8 min read

SwiftUI Cheat Sheet: Views, State & Navigation (iOS 18)

Leo
By Leo
| Updated: May 19, 2026
SwiftUI Cheat Sheet: Views, State & Navigation (iOS 18)
TL;DR
  • iOS 18 / Swift 6: @Observable replaces @ObservableObject, NavigationStack replaces NavigationView
  • Layout: VStack / HStack / ZStack for basic, Grid + GridRow for complex
  • State flows down via @State@Binding; shared state via @Observable class + @Environment
  • SwiftData + @Model replaces most CoreData patterns
  • All SwiftUI views are value types — mutations must go through state

Quick reference tables

Text & display

View / ModifierExampleNotes
TextText("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
MarkdownText("**Bold** _italic_")Supported natively
LabelLabel("Settings", systemImage: "gear")Icon + text pair
ImageImage(systemName: "star.fill")SF Symbols
AsyncImageAsyncImage(url: url)Remote image with built-in loading

Layout containers

ContainerUse for
VStack(alignment: .leading, spacing: 8)Vertical stack
HStack(alignment: .center, spacing: 12)Horizontal stack
ZStack(alignment: .bottomTrailing)Overlapping views
LazyVStack / LazyHStackLarge lists (deferred rendering)
Grid + GridRowTable-like 2D layout
ViewThatFitsPicks first view that fits in available space
ScrollViewScrollable container (vertical by default)
ScrollView(.horizontal)Horizontal scroll
ListRows with built-in separators and swipe actions
FormSettings-style grouped rows
GroupBoxGrouped content with a border
Spacer()Flexible space that fills available room
Divider()Horizontal separator line

Common modifiers

ModifierExample
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

plaintext
@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

swift
struct CounterView: View {
    @State private var count = 0

    var body: some View {
        Button("Count: \(count)") {
            count += 1
        }
    }
}

@Binding — pass state to a child

swift
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)

Replaces the old @ObservableObject / @Published pattern:

swift
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 vs @ObservableObject

@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

swift
// 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

swift
struct SettingsView: View {
    @AppStorage("darkMode") var darkMode = false

    var body: some View {
        Toggle("Dark Mode", isOn: $darkMode)
    }
}

swift
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

swift
@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

swift
TabView {
    HomeView()
        .tabItem { Label("Home", systemImage: "house") }

    SearchView()
        .tabItem { Label("Search", systemImage: "magnifyingglass") }

    ProfileView()
        .tabItem { Label("Profile", systemImage: "person") }
}

Sheet and full-screen cover

swift
@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

swift
List(fruits, id: \.self) { fruit in
    Text(fruit)
}

// Section headers
List {
    Section("Favorites") {
        ForEach(favorites) { item in
            ItemRow(item: item)
        }
    }
}

Swipe actions

swift
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

swift
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

swift
@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

swift
Button("Animate") {
    withAnimation(.easeInOut(duration: 0.3)) {
        isVisible.toggle()
        scale = isVisible ? 1.0 : 0.5
    }
}

Common animation curves

ModifierBehavior
.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)

swift
if isVisible {
    CardView()
        .transition(.asymmetric(
            insertion: .slide,
            removal: .opacity
        ))
}

Common patterns

Conditional view

swift
// Use if/else inside ViewBuilder
VStack {
    if isLoading {
        ProgressView()
    } else {
        ContentView()
    }
}

Safe area and keyboard avoidance

swift
.safeAreaInset(edge: .bottom) {
    ComposerBar()   // Floats above tab bar / keyboard
}

.scrollDismissesKeyboard(.interactively)

task modifier (async on appear)

swift
.task {
    await viewModel.loadData()
}

// With ID — re-runs when ID changes:
.task(id: selectedTab) {
    await viewModel.reload(for: selectedTab)
}

UIKit migration table

UIKitSwiftUI equivalent
UILabelText
UIButtonButton
UIImageViewImage / AsyncImage
UITextFieldTextField
UITextViewTextEditor
UITableViewList
UICollectionViewLazyVGrid / LazyHGrid
UIScrollViewScrollView
UINavigationControllerNavigationStack
UITabBarControllerTabView
UIAlertController.alert() modifier
UIActivityIndicatorViewProgressView()
UISliderSlider
UISwitchToggle
UIDatePickerDatePicker
UISegmentedControlPicker(.segmented)
viewDidAppear.onAppear { }
viewDidDisappear.onDisappear { }
@IBOutlet / @IBActionState + binding
NSFetchedResultsController@Query (SwiftData)

Summary

  • @State for local mutations, @Binding to pass down, @Observable for shared state
  • NavigationStack + .navigationDestination replaces all navigation patterns from before iOS 16
  • SwiftData + @Query is 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.