Irbisa · Cheatsheet
SwiftUI State Management
@State, @Binding, @StateObject, @ObservedObject, @EnvironmentObject, Observation
What is the difference between
@Stateand@Bindingin SwiftUI?struct CounterScreen: View { @State private var count = 0 // owned here var body: some View { VStack { Text("\(count)")…In this view, what do
count,$countand_counteach refer to?struct CounterView: View { @State private var count: Int init(start: Int = 0) { _count = State(initialValue: start) } var body: some View { VStack { Text("\(count)") Stepper("Count", value: $count) Button("Reset") { count = 0 } } }}What is the difference between
@Environment,@EnvironmentObjectand@ObservedObject, and when do you use the environment instead ofinit?@Observablefinal class AppSession { var userName: String?} @main…Which
@Environmentvalues do you use in everyday code? For example, how does a child view dismiss its own screen?struct EditNoteScreen: View { @Environment(\.dismiss) private var dismiss @Environment(\.scenePhase) private var scenePhase @State private var text = "" var body: some View {…The Done button on your form's keyboard does nothing and Next never jumps to the following field. Which property wrapper is missing?
struct SignUpForm: View { enum Field { case email, password } @State private var email = "" @State private var password = "" @FocusState private var focused: Field?…The parent changes
startfrom 0 to 5, yetCounterkeeps showing 0. Why, and how do you fix it?struct Counter: View { @State private var count: Int init(start: Int) { _count = State(initialValue: start) } var body: some View { Button("\(count)") { count += 1 } }} // In the parent:// Counter(start: selectedPreset.start)When do you use
@StateObjectvs@ObservedObject, and what breaks if you pick the wrong one?final class CartViewModel: ObservableObject { @Published var items: [String] = []} // Owner: created once, kept for the view's lifetimestruct CartScreen: View {…What does
@Publisheddo, and how does anObservableObjectannounce changes?import Combine final class SearchModel: ObservableObject { @Published var query = "" private var bag = Set<AnyCancellable>()…Find the bug: tapping a product adds it to the cart, but the cart counter in the toolbar does not change.
final class Cart: ObservableObject { @Published var items: [Product] = [] }final class ShopModel: ObservableObject { @Published var cart = Cart() } struct ShopScreen: View { let products: [Product] @StateObject private var model = ShopModel() var body: some View { List(products) { p in Button(p.name) { model.cart.items.append(p) } } .toolbar { Text("Cart: \(model.cart.items.count)") } }}What does the
@Observablemacro change compared withObservableObjectand@Published?@Observablefinal class FeedModel { var title = "Feed" var posts: [Post] = [] @ObservationIgnored var lastRequestID: UUID? // not tracked}…What is
@Bindable, and when do you need it with an@Observablemodel?@Observablefinal class Profile { var name = "" var isPublic = false}…You tap the button and the text never changes. Why, and what is the fix?
final class Draft { var title = ""} struct Editor: View { @State private var draft = Draft() var body: some View { VStack { Text(draft.title.isEmpty ? "empty" : draft.title) Button("Set") { draft.title = "Hello" } } }}How do you choose the right property wrapper for a piece of state in SwiftUI?
How do you hand a child view a
Bindingwhen the value is not stored in@State?struct ProfileEditor: View { let user: User // @Observable class with age: Int @State private var nickname: String? var body: some View { Form {…Edits made on a detail screen never appear in the list behind it, even though both draw the same
Orderstruct. What went wrong?struct Order: Identifiable { let id: UUID; var note: String } // wrong: the detail edits a private copystruct CopyDetail: View { @State private var order: Order init(order: Order) { _order = State(initialValue: order) }…When does SwiftUI re-run a view's
body, and when does the view's@Stateget reset?struct EditorScreen: View { @State private var isEditing = false var body: some View { // Two branches = two views: switching resets FormView's @State if isEditing {…A row's expanded/collapsed toggle keeps resetting after the user scrolls it off screen and back. What is actually happening?
struct Feed: View { let items: [Item] @State private var expanded: Set<Item.ID> = [] // survives rows being discarded var body: some View { List(items) { item in…Where do side effects and async work belong in a SwiftUI view, and what happens to a
.taskwhen the view disappears?struct SearchScreen: View { @State private var query = "" @State private var results: [Hit] = [] let api: SearchAPI var body: some View {…What do
@AppStorageand@SceneStoragedo, and where do they stop being the right tool?enum Theme: String, CaseIterable { case system, light, dark } struct SettingsView: View { @AppStorage("showBadges") private var showBadges = true @AppStorage("theme") private var theme: Theme = .system // RawRepresentable @SceneStorage("draftNote") private var draftNote = "" // restored per window…How does
@Observableknow which views to invalidate, and what kinds of change does that tracking silently miss?// Roughly what @Observable generates for one propertyfinal class Model: Observable { @ObservationIgnored private var _title = "" @ObservationIgnored private let registrar = ObservationRegistrar() var title: String {…Your model writes its properties from a background network callback and the list updates late, glitches, or crashes. What does SwiftUI actually require here?
// wrong: writes an observed property from a background callback@Observable final class BadFeed { var items: [Item] = [] func load(_ url: URL) { URLSession.shared.dataTask(with: url) { data, _, _ in self.items = decode(data) // background thread: data race…Find the bug: the chat server logs a new connection every time the parent screen redraws, though the user has only one chat open.
// ChatSocket wraps a WebSocket: connect() opens it, disconnect() closes it, messages() streams incoming text@MainActor @Observable final class ChatModel { var messages: [String] = [] private let socket = ChatSocket(url: supportURL) init() { Task { // bug: side effects in init socket.connect() for await message in socket.messages() { messages.append(message) } } }} struct SupportChatScreen: View { @State private var model = ChatModel() var body: some View { List(model.messages, id: \.self) { Text($0) } }}A SwiftUI screen stutters and you suspect too many body evaluations. How do you confirm it and what do you change?
struct CartRow: View { let item: CartItem var body: some View { let _ = Self._printChanges() // debug only: prints `@self`, `@identity` or the changed property Text(item.title)…How would you organise state ownership and data flow in a large SwiftUI app?