All articles
Article 3 min read

A Guide to Understanding MVVM by Building a Simple Weather App with SwiftUI

This guide will help you understand Model-View-ViewModel (MVVM) architecture through creating a basic weather app using SwiftUI.

Introduction

Model-View-ViewModel (MVVM) is an architectural pattern that separates the data layer from the UI. Understanding MVVM can be challenging, especially when transitioning to newer UI frameworks like SwiftUI. This guide will illustrate how to implement MVVM with SwiftUI by building a simple weather application. The aim is not only to create functional code but also to deepen understanding of each component's role in the MVVM pattern.

Model Layer

In our basic example, we'll model the data required for displaying weather information. For simplicity, let's use Swift's built-in WeatherData struct which contains properties like temperature and description:

swift
struct WeatherData {
    var temperature: Double // Degrees Celsius
    var condition: String  // e.g., "sunny", "cloudy"
}

This struct represents the data model that will be used to display weather information in our app.

View Layer

The SwiftUI WeatherView struct serves as the main view, which displays temperature and description. We'll use a Text for displaying these values. To make it MVVM-compliant, we'll encapsulate this content within a @StateObject property wrapper:

swift
struct WeatherView: View {
    @ObservedObject var viewModel: WeatherViewModel // The ViewModel will manage the data flow

    init(viewModel: WeatherViewModel) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    var body: some View {
        VStack(alignment: .leading, spacing: 8) {
            Text("\(viewModel.currentTemperature)")
                .font(.headline)

            Text(viewModel.condition)
                .font(.subheadline)
        }.padding()
    }
}

In this example, @ObservedObject is used to link the view with its associated ViewModel. This connection allows changes in data (from our Model layer) to reflect seamlessly on the UI.

ViewModel Layer

The core of MVVM lies within the ViewModel which handles all business logic and interacts between model and view layers. In SwiftUI, we use @StateObject for this purpose:

swift
class WeatherViewModel: ObservableObject {
    @Published var currentTemperature: Double = 0.0
    @Published var condition: String = ""

    private let weatherDataService: WeatherDataService

    init(weatherDataService: WeatherDataService) {
        self.weatherDataService = weatherDataService
        fetchWeather()
    }

    func fetchWeather() {
        guard let data = try? await weatherDataService.fetchWeather(), !data.isEmpty else {
            print("No valid data received. Current Temperature: \(currentTemperature), Condition: '\(condition)'")
            return
        }
        
        self.currentTemperature = data.temperature
        self.condition = data.condition
        
    }
}

Here, @Published properties ensure the ViewModel can notify its subscribers about any changes. fetchWeather() fetches and updates these published properties from a service that could be implemented in different ways (e.g., using URLSession to make network requests).

Putting it Together

To complete our weather app, we need to integrate all components: The WeatherData struct provides the data, the WeatherView struct displays this data, and finally, the WeatherViewModel manages fetching and updating these values. Here's how the structure ties together:

swift
struct ContentView: View {
    @StateObject var viewModel = WeatherViewModel(weatherDataService: WeatherDataService())
    
    var body: some View {
        NavigationView {
            WeatherView(viewModel: viewModel)
                .navigationTitle("Current Conditions")
        }
    }
}

In ContentView, we create a new instance of our WeatherViewModel and ensure its proper lifecycle management using @StateObject. This also ensures that any updates or data fetches trigger the view to update correctly.

This guide provided a step-by-step demonstration on how to implement MVVM with SwiftUI. By understanding each layer's purpose, you'll be better equipped to apply this pattern when building applications that require clean separation of concerns and easy maintenance.

Feel free to explore adding more functionality like different locations, historical weather data, or even user preferences – all while adhering to MVVM principles!