As your SwiftUI app grows, putting all your logic, networking, and data formatting directly inside your Views becomes a massive mess.
To keep code clean, testable, and highly organized, developers use design patterns.
The most popular and highly recommended architecture for SwiftUI is MVVM (Model-View-ViewModel).
MVVM separates your application into three distinct layers:
User struct).ContentView).The golden rule of MVVM is: Views should not do any heavy lifting. They should only display what the ViewModel tells them to display.
A ViewModel is typically created as a class that conforms to the ObservableObject protocol.
Inside, you mark any data that the UI needs to display with the @Published property wrapper.
import Foundationclass CounterViewModel: ObservableObject { // The view will automatically update when this changes @Published var count: Int = 0 // Business logic belongs here, not in the View! func increment() { count += 1 } func reset() { count = 0 } }
To connect your UI to the ViewModel, you instantiate it inside your SwiftUI View using the @StateObject property wrapper.
@StateObject ensures that SwiftUI keeps the ViewModel alive in memory even if the View itself is destroyed and redrawn.
import SwiftUIstruct CounterView: View { // Creating and owning the ViewModel @StateObject private var viewModel = CounterViewModel() var body: some View { VStack(spacing: 20) { // Reading data from the ViewModel Text("Count: \(viewModel.count)") .font(.largeTitle) // Triggering intents (actions) on the ViewModel Button("Add 1") { viewModel.increment() } } } }
By moving functions like increment() out of the View and into the ViewModel, you make your code infinitely easier to test.
You can write a simple unit test for CounterViewModel without needing to simulate button taps or UI rendering!
In the MVVM pattern, which component is responsible for holding business logic and notifying the UI of data changes?