0Pricing
Swift Academy · Lesson

Binding View Models to Views

Connect view models with SwiftUI and Combine.

Binding View Models to Views is a free Swift Academy lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Swift Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Connecting ViewModel to View

A ViewModel is only useful when the View reacts to its changes. In SwiftUI this reactivity is powered by the Combine-based property wrappers @Published, @StateObject, and @ObservedObject.

Together they form an automatic binding: when the ViewModel changes, the View re-renders.

ObservableObject

To be observed by SwiftUI, a ViewModel must conform to ObservableObject. This protocol gives it an objectWillChange publisher that fires before any change.

final class CounterViewModel: ObservableObject {
    @Published var count: Int = 0

    func increment() {
        count += 1
    }
}

@Published Properties

Marking a property with @Published automatically emits a change notification whenever its value is set.

Every @Published property feeds into the object's objectWillChange publisher, so the View knows exactly when to refresh.

final class SearchViewModel: ObservableObject {
    @Published var query: String = ""
    @Published var results: [String] = []
}

@StateObject

@StateObject tells a View to create and own a ViewModel. SwiftUI instantiates it once and keeps it alive across re-renders of that View.

Use @StateObject where the ViewModel is first created.

struct CounterView: View {
    @StateObject private var viewModel = CounterViewModel()

    var body: some View {
        Button("Count: \(viewModel.count)") {
            viewModel.increment()
        }
    }
}

@ObservedObject

@ObservedObject is for a ViewModel that was created elsewhere and passed in. The View observes it but does not own its lifecycle.

Use it for child views that receive a ViewModel from a parent.

struct BadgeView: View {
    @ObservedObject var viewModel: CounterViewModel

    var body: some View {
        Text("Total: \(viewModel.count)")
    }
}

StateObject vs ObservedObject

The critical difference is ownership:

  • @StateObject — the View owns it; survives re-renders
  • @ObservedObject — passed in; the View does not own it

Using @ObservedObject where you should use @StateObject can cause the object to be recreated and lose its state on every parent update.

Passing a ViewModel Down

Create the ViewModel once with @StateObject in the parent, then pass it to children that mark it @ObservedObject.

struct ParentView: View {
    @StateObject private var viewModel = CounterViewModel()

    var body: some View {
        VStack {
            BadgeView(viewModel: viewModel)
            Button("Add") { viewModel.increment() }
        }
    }
}

Two-Way Bindings

For inputs like text fields, you need a two-way Binding. SwiftUI generates one from a @Published property using the $ prefix on the wrapper.

struct SearchView: View {
    @StateObject private var viewModel = SearchViewModel()

    var body: some View {
        TextField("Search", text: $viewModel.query)
    }
}

Driving UI State

The View should derive everything it shows from published properties. Loading spinners, error banners, and content all flow from ViewModel state.

final class FeedViewModel: ObservableObject {
    enum State { case loading, loaded([String]), error(String) }
    @Published var state: State = .loading
}

Reacting to State

The View switches over the ViewModel state to render the right UI. There is no imperative show/hide code, just a declarative mapping.

struct FeedView: View {
    @StateObject var viewModel = FeedViewModel()
    var body: some View {
        switch viewModel.state {
        case .loading: ProgressView()
        case .loaded(let items): List(items, id: \.self) { Text($0) }
        case .error(let message): Text(message)
        }
    }
}

Keeping Updates on Main

SwiftUI requires published changes on the main thread. When a ViewModel updates state after async work, annotate it with @MainActor to guarantee this.

@MainActor
final class ProfileViewModel: ObservableObject {
    @Published var name: String = ""

    func load() async {
        name = await fetchName()
    }
}

Quick Check

Test your binding knowledge.

Recap

You connected ViewModels to Views using SwiftUI bindings:

  • ObservableObject + @Published emit change notifications
  • @StateObject creates and owns a ViewModel
  • @ObservedObject observes one passed in
  • @MainActor keeps async updates on the main thread

With reactive bindings in place, the View always reflects the latest ViewModel state automatically.

Frequently asked questions

Is the “Binding View Models to Views” lesson free?

Yes — the full text of “Binding View Models to Views” is free to read here on the web, and the Swift Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Swift Academy course, upgrade to CoddyKit PRO.

What will I learn in “Binding View Models to Views”?

Connect view models with SwiftUI and Combine. You practise Swift Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start Swift Academy?

No prior experience is required. Swift Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Binding View Models to Views” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this Swift Academy lesson?

Yes. Every Swift Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. The MVVM Pattern
  2. Binding View Models to Views
  3. The Coordinator Pattern
  4. Testing View Models
← Back to Swift Academy