What is MVVM?
MVVM (Model-View-ViewModel) is a design pattern that helps separate concerns in an application by organizing code into three distinct layers:
- Model: Represents data and business logic.
- View: The UI layer that presents data to the user.
- ViewModel: Acts as an intermediary between the Model and View, handling data transformation and business logic.
Why Use MVVM in SwiftUI?
- SwiftUI is declarative, making MVVM a natural fit for managing state efficiently.
- Improves testability by keeping business logic separate from the UI.
- Enhances maintainability and scalability.
Project: Health Tracker App (Using HealthKit)
Step 1: Setting Up the Project
- Open Xcode and create a new SwiftUI App.
- Enable HealthKit in your project:
- Go to Signing & Capabilities > + Capability > Add HealthKit.
Step 2: Creating the Model
We define a HealthDataModel
that represents the health data we will track (e.g., steps, heart rate).
import HealthKit
struct HealthDataModel {
var stepCount: Int
var heartRate: Double
}
Step 3: Setting Up the ViewModel
The ViewModel interacts with HealthKit and provides data to the View.
import Foundation
import HealthKit
class HealthViewModel: ObservableObject {
private var healthStore = HKHealthStore()
@Published var healthData = HealthDataModel(stepCount: 0, heartRate: 0.0)
init() {
requestAuthorization()
}
private func requestAuthorization() {
let stepType = HKQuantityType.quantityType(forIdentifier: .stepCount)!
let heartRateType = HKQuantityType.quantityType(forIdentifier: .heartRate)!
let typesToRead: Set = [stepType, heartRateType]
healthStore.requestAuthorization(toShare: nil, read: typesToRead) { success, error in
if success {
self.fetchStepCount()
self.fetchHeartRate()
}
}
}
func fetchStepCount() {
guard let stepType = HKQuantityType.quantityType(forIdentifier: .stepCount) else { return }
let query = HKStatisticsQuery(quantityType: stepType, quantitySamplePredicate: nil, options: .cumulativeSum) { _, result, _ in
guard let sum = result?.sumQuantity() else { return }
DispatchQueue.main.async {
self.healthData.stepCount = Int(sum.doubleValue(for: HKUnit.count()))
}
}
healthStore.execute(query)
}
func fetchHeartRate() {
guard let heartRateType = HKQuantityType.quantityType(forIdentifier: .heartRate) else { return }
let query = HKSampleQuery(sampleType: heartRateType, predicate: nil, limit: 1, sortDescriptors: [NSSortDescriptor(key: HKSampleSortIdentifierStartDate, ascending: false)]) { _, samples, _ in
guard let sample = samples?.first as? HKQuantitySample else { return }
DispatchQueue.main.async {
self.healthData.heartRate = sample.quantity.doubleValue(for: HKUnit(from: "count/min"))
}
}
healthStore.execute(query)
}
}
Step 4: Building the View
The View displays the step count and heart rate, updating as data changes.
import SwiftUI
struct HealthView: View {
@StateObject private var viewModel = HealthViewModel()
var body: some View {
VStack {
Text("Step Count: \(viewModel.healthData.stepCount)")
.font(.title)
.padding()
Text("Heart Rate: \(String(format: "%.1f", viewModel.healthData.heartRate)) BPM")
.font(.title)
.padding()
}
.onAppear {
viewModel.fetchStepCount()
viewModel.fetchHeartRate()
}
}
}
struct ContentView: View {
var body: some View {
HealthView()
}
}
@main
struct HealthApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
Step 5: Running the App
- Run the app on a physical device (HealthKit does not work on the simulator).
- Allow HealthKit permissions when prompted.
- See real-time updates for step count and heart rate.
Best Practices for Using MVVM in SwiftUI
- Keep the ViewModel lightweight: It should only manage data transformations and not handle heavy computations.
-
Use Combine for reactive programming:
@Published
helps keep UI in sync with data changes. - Separate concerns: Avoid adding too much logic inside the View.