Irbisa · Cheatsheet

SwiftUI State Management

@State, @Binding, @StateObject, @ObservedObject, @EnvironmentObject, Observation

Middle Developer · 24 questions

  1. 01

    What is the difference between @State and @Binding in SwiftUI?

    @State is storage a view owns: SwiftUI keeps the value outside the view struct and re-renders the view when it changes.

    struct CounterScreen: View {  @State private var count = 0            // owned here   var body: some View {    VStack {      Text("\(count)")…
  2. 02

    In this view, what do count, $count and _count each 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 }    }  }}

    count is the current value (the wrapper's wrappedValue), $count is a Binding<Int> to it (the projectedValue), and _count is the State<Int> wrapper itself, which you only touch in init.

  3. 03

    What is the difference between @Environment, @EnvironmentObject and @ObservedObject, and when do you use the environment instead of init?

    @ObservedObject receives an object explicitly through the initializer, while @EnvironmentObject and @Environment read something injected higher up the tree, so the views in between do not have to pass it along (prop drilling).

    @Observablefinal class AppSession {  var userName: String?} @main…
  4. 04

    Which @Environment values do you use in everyday code? For example, how does a child view dismiss its own screen?

    It reads @Environment(\.dismiss) and calls it.

    struct EditNoteScreen: View {  @Environment(\.dismiss) private var dismiss  @Environment(\.scenePhase) private var scenePhase  @State private var text = ""   var body: some View {…
  5. 05

    The Done button on your form's keyboard does nothing and Next never jumps to the following field. Which property wrapper is missing?

    @FocusState (together with .onSubmit) is missing.

    struct SignUpForm: View {  enum Field { case email, password }   @State private var email = ""  @State private var password = ""  @FocusState private var focused: Field?…
  6. 06

    The parent changes start from 0 to 5, yet Counter keeps 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)

    State(initialValue:) is used only the first time a view with this identity appears.

  7. 07

    When do you use @StateObject vs @ObservedObject, and what breaks if you pick the wrong one?

    @StateObject means the view owns the object: SwiftUI creates it once and keeps it alive for the view's lifetime.

    final class CartViewModel: ObservableObject {  @Published var items: [String] = []} // Owner: created once, kept for the view's lifetimestruct CartScreen: View {…
  8. 08

    What does @Published do, and how does an ObservableObject announce changes?

    @Published wraps a property and emits on the object's objectWillChange publisher just before the value changes; views observing the object (@StateObject, @ObservedObject, @EnvironmentObject) then re-run body.

    import Combine final class SearchModel: ObservableObject {  @Published var query = ""  private var bag = Set<AnyCancellable>()…
  9. 09

    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)") }  }}

    Cart is a class, so @Published var cart holds a reference that never changes.

  10. 10

    What does the @Observable macro change compared with ObservableObject and @Published?

    @Observable makes every stored property of a class observable without @Published, and SwiftUI tracks which properties each view actually read, so a view re-renders only when those properties change instead of on any change to the object.

    @Observablefinal class FeedModel {  var title = "Feed"  var posts: [Post] = []  @ObservationIgnored var lastRequestID: UUID?   // not tracked}…
  11. 11

    What is @Bindable, and when do you need it with an @Observable model?

    @Bindable turns an @Observable object into a source of bindings: $profile.name becomes a Binding<String> for a TextField.

    @Observablefinal class Profile {  var name = ""  var isPublic = false}…
  12. 12

    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" }    }  }}

    @State re-renders the view when the value it stores is replaced, and draft.title = "Hello" changes the object behind the reference without replacing the reference, so SwiftUI sees no change.

  13. 13

    How do you choose the right property wrapper for a piece of state in SwiftUI?

    The view that creates the state owns it: @State for a value or an @Observable model.

  14. 14

    How do you hand a child view a Binding when the value is not stored in @State?

    A Binding is just a getter plus a setter, so you can build one with Binding(get:set:) whenever the value lives where a $ cannot reach: behind a type conversion or inside an optional.

    struct ProfileEditor: View {  let user: User                 // @Observable class with age: Int  @State private var nickname: String?   var body: some View {    Form {…
  15. 15

    Edits made on a detail screen never appear in the list behind it, even though both draw the same Order struct. What went wrong?

    The detail screen got a copy.

    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) }…
  16. 16

    When does SwiftUI re-run a view's body, and when does the view's @State get reset?

    SwiftUI re-runs body when something the view read has changed: its state, a binding, an environment value, an observed property, or the inputs its parent passed.

    struct EditorScreen: View {  @State private var isEditing = false   var body: some View {    // Two branches = two views: switching resets FormView's @State    if isEditing {…
  17. 17

    A row's expanded/collapsed toggle keeps resetting after the user scrolls it off screen and back. What is actually happening?

    The flag lives in the row's @State, and a List or LazyVStack is free to destroy rows that are far off screen.

    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…
  18. 18

    Where do side effects and async work belong in a SwiftUI view, and what happens to a .task when the view disappears?

    Never in body, which SwiftUI may run at any time and any number of times.

    struct SearchScreen: View {  @State private var query = ""  @State private var results: [Hit] = []  let api: SearchAPI   var body: some View {…
  19. 19

    What do @AppStorage and @SceneStorage do, and where do they stop being the right tool?

    @AppStorage is a property backed by UserDefaults that re-renders the view when the key changes; @SceneStorage is per-window UI state that the system saves and restores when the app is relaunched.

    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…
  20. 20

    How does @Observable know which views to invalidate, and what kinds of change does that tracking silently miss?

    The macro turns each stored property into a computed one that reports reads and writes to an ObservationRegistrar, and SwiftUI runs body inside observation tracking, so a view depends on exactly the properties it touched during that run.

    // Roughly what @Observable generates for one propertyfinal class Model: Observable {  @ObservationIgnored private var _title = ""  @ObservationIgnored private let registrar = ObservationRegistrar()   var title: String {…
  21. 21

    Your model writes its properties from a background network callback and the list updates late, glitches, or crashes. What does SwiftUI actually require here?

    Every write a view can observe has to happen on the main actor.

    // 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…
  22. 22

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

    @State evaluates its initial value every time the view struct is created, and SwiftUI recreates SupportChatScreen on every parent redraw.

  23. 23

    A SwiftUI screen stutters and you suspect too many body evaluations. How do you confirm it and what do you change?

    First prove which views re-render and why, then fix the cause.

    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)…
  24. 24

    How would you organise state ownership and data flow in a large SwiftUI app?

    Give every piece of state exactly one owner at the narrowest scope that needs it.