NewThe Flutter Kit — Flutter boilerplate$149$69
iOS Architecture · 2026 Edition

MVVM in Swift — Complete Guide for SwiftUI Developers

What MVVM means in Swift in 2026, why it\'s still relevant with @Observable, and the production architecture indie iOS developers actually ship.

Last updated: 2026-05-16 8 min read By Ahmed Gagan, iOS Engineer
Quick Answer

MVVM (Model-View-ViewModel) in Swift is an architectural pattern where the Model holds domain data, the View renders UI, and the ViewModel mediates between them. In SwiftUI 2026, the ViewModel is an @Observable class that owns view state and async work; the View reads it via @State or @Environment. MVVM remains the default architecture for SwiftUI iOS apps — TCA is the heavier alternative for teams who want it.

Modern pattern
@Observable class as ViewModel
Replaces
ObservableObject + @Published (legacy)
Alternative
TCA (for teams), MV (Apple sample style)
Min iOS
17+ for @Observable macro

The Three Layers

MVVM splits an app into three distinct responsibilities:

  • Model — plain Swift structs / classes representing domain data (Habit, Message, User). No SwiftUI imports.
  • View — SwiftUI views that render UI. No business logic, no async work, no formatting beyond basic display.
  • ViewModel — @Observable class that holds view state, formats data for display, and handles user intents (button taps, async calls). Imports Foundation, optionally Combine. No SwiftUI imports.

A Production ViewModel in SwiftUI 2026

Here's the @Observable pattern that replaces the old ObservableObject + @Published combo:

TaskListViewModel.swift
import Foundation
import Observation

@Observable
@MainActor
final class TaskListViewModel {
  var tasks: [Task] = []
  var isLoading = false
  var errorMessage: String?

  private let repository: TaskRepository

  init(repository: TaskRepository) {
    self.repository = repository
  }

  func load() async {
    isLoading = true
    defer { isLoading = false }
    do {
      tasks = try await repository.fetchTasks()
    } catch {
      errorMessage = error.localizedDescription
    }
  }

  func complete(_ task: Task) async {
    try? await repository.markComplete(task.id)
    await load()
  }
}

Binding the View

Views read @Observable state directly. Use @State if the view owns the ViewModel lifecycle; @Environment if it's shared.

TaskListView.swift
struct TaskListView: View {
  @State private var viewModel = TaskListViewModel(repository: .live)

  var body: some View {
    List(viewModel.tasks) { task in
      TaskRow(task: task) {
        Task { await viewModel.complete(task) }
      }
    }
    .overlay {
      if viewModel.isLoading { ProgressView() }
    }
    .task { await viewModel.load() }
  }
}

When to Skip the ViewModel

Don't over-architect. Skip the ViewModel for:

  • Pure UI state — form fields, toggles, scroll position, sheet visibility
  • Single-line view bodies — `Text(item.title)` doesn't need a ViewModel
  • Tutorials / Apple sample code — they're intentionally compact
  • Component-level state — keep it in @State on the component
  • But: any view that does async work, formatting, or business logic → ViewModel

Why MVVM Over TCA

Both work; pick by team and complexity:

  • MVVM is the default — smaller mental model, faster onboarding
  • TCA adds testability + time-travel debugging at the cost of significant boilerplate
  • For indie / solo developers: MVVM is the right call
  • For 5+ engineer teams with strong functional background: TCA pays off
  • You can mix — use MVVM by default and TCA in specific modules if needed

MVVM done right — out of the box.

The Swift Kit uses @Observable view models across every module, with consistent architecture and dependency injection patterns.

Get The Swift Kit — $99

Frequently Asked Questions

What is MVVM in Swift?
MVVM (Model-View-ViewModel) is an architectural pattern where the Model holds your domain data, the View renders UI, and the ViewModel mediates between them — exposing observable state and intent-handling methods. In Swift / SwiftUI in 2026, the ViewModel is typically an @Observable class that the View reads via @State or @Environment.
Is MVVM still relevant in SwiftUI with @Observable?
Yes. @Observable replaced ObservableObject + @Published but didn't replace MVVM — it modernized it. A View still benefits from a separate ViewModel for testability, multi-screen state sharing, and async work coordination. For simple views with no async work, you can skip the ViewModel and use @State directly.
When should I NOT use MVVM in SwiftUI?
Simple views that only contain local UI state (toggles, form fields, scroll position). Use @State directly. The "Apple style" SwiftUI sample code often skips ViewModels for compact examples — that's fine for tutorials but rarely scales to production apps.
What's the difference between MVVM and MVC in iOS?
MVC (Apple's historical pattern, dominant in UIKit) put view logic in UIViewController, which became a "Massive View Controller" anti-pattern. MVVM splits view logic into a separate ViewModel, making it testable and decoupling it from view lifecycle. SwiftUI implicitly steers you toward MVVM.
Is MVVM or TCA better for SwiftUI?
For indie apps: MVVM with @Observable. Lower complexity, smaller learning curve, less ceremony. TCA (The Composable Architecture) is excellent for teams already familiar with it, especially at scale. Don't adopt TCA solo unless you specifically want to invest in learning it.
Does The Swift Kit use MVVM?
Yes — The Swift Kit uses @Observable view models for screens with non-trivial state (chat, paywall, auth, onboarding) and pure SwiftUI @State for simpler views. The architecture is documented and consistent across every module.

Keep exploring

Ship your iOS app 10× faster

The Swift Kit gives you a production-ready SwiftUI boilerplate — design system, paywall, auth, AI, all pre-wired. $99 one-time.

Get The Swift Kit — $99

One-time purchase · Lifetime updates · 14-day refund