Skip to content

Streaming Changes with Observations (SwiftUI)

A SwiftUI guide for coding agents. Also covers observations async sequence, observable to async stream, withobservationtracking limits, observe model changes for await, combine publisher replacement, keypath observation stream.

The Observations type turns an @Observable model into an AsyncSequence, so you consume changes with for await instead of Combine publishers or manual observation tracking.

Why withObservationTracking is not enough

withObservationTracking fires onChange once, for the first change only, so streaming requires re-registering recursively. It also cannot feed an asynchronous for-loop.

func startObservation() {
  withObservationTracking {
    render(store.state)
  } onChange: {
    Task { startObservation() }   // manual re-registration, every change
  }
}

Observations: touch properties, get a stream

The closure you pass to Observations implicitly observes every observable property it reads. Each change emits the closure's fresh return value into the async sequence.

let store = Store()   // @Observable

struct State {
  let items: [String]
  let isLoading: Bool
}

let streamOfStates = Observations {
  State(items: store.items, isLoading: store.isLoading)
}

for await state in streamOfStates {
  render(state)
}

Updates are transactional: when items and isLoading change together, the stream emits one combined value, not one per property.

A KeyPath convenience

A small extension gives any observable a per-property stream.

extension Observable {
  func stream<Value: Sendable>(
    of keyPath: KeyPath<Self, Value>
  ) -> any AsyncSequence<Value, Never> {
    Observations { self[keyPath: keyPath] }
  }
}

for await items in store.stream(of: \.items) {
  print(items)
}

Use this guidance in your coding agent

Install the Better Design MCP once. Your agent then loads this page with one call.

get-swiftui-guide({ topic: "swiftui-streaming-changes-with-observations" })
claude mcp add --scope user better-design --transport http https://better-design.com/api/mcp --header "Authorization: Bearer <YOUR_API_KEY>"
Browse related design systems