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

  1. Open Xcode and create a new SwiftUI App.
  2. 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

  1. Run the app on a physical device (HealthKit does not work on the simulator).
  2. Allow HealthKit permissions when prompted.
  3. 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.