Mastering MVVM With Swift

What Is Wrong With Cloudy

Resources

Now that you have an idea of the ins and outs of the project, I would like to take a few minutes to highlight some of the problems the project suffers from. Because we are working with a small project, the problems we resolve in this series are subtle. That is why we take a few minutes to explore them in this episode.

Day View Controller

We start with the day view controller. The first thing to point out is that the controller keeps a reference to the model. This is a classic example of the Model-View-Controller pattern. Even though there isn't anything inherently wrong with this pattern, a controller referencing a model is a red flag when we adopt the Model-View-ViewModel pattern.

DayViewController.swift

var now: WeatherData? {
    didSet {
        updateView()
    }
}

The second and most important problem is the implementation of the updateWeatherDataContainerView(with:) method. This is another example that is typical for the Model-View-Controller pattern. The raw values of the model object are transformed and formatted before they are displayed to the user.

DayViewController.swift

private func updateWeatherDataContainerView(with weatherData: WeatherData) {
    weatherDataContainerView.isHidden = false

    var windSpeed = weatherData.windSpeed
    var temperature = weatherData.temperature

    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "EEE, MMMM d"
    dateLabel.text = dateFormatter.string(from: weatherData.time)

    let timeFormatter = DateFormatter()

    if UserDefaults.timeNotation == .twelveHour {
        timeFormatter.dateFormat = "hh:mm a"
    } else {
        timeFormatter.dateFormat = "HH:mm"
    }

    timeLabel.text = timeFormatter.string(from: weatherData.time)

    descriptionLabel.text = weatherData.summary

    if UserDefaults.temperatureNotation != .fahrenheit {
        temperature = temperature.toCelcius
        temperatureLabel.text = String(format: "%.1f °C", temperature)
    } else {
        temperatureLabel.text = String(format: "%.1f °F", temperature)
    }

    if UserDefaults.unitsNotation != .imperial {
        windSpeed = windSpeed.toKPH
        windSpeedLabel.text = String(format: "%.f KPH", windSpeed)
    } else {
        windSpeedLabel.text = String(format: "%.f MPH", windSpeed)
    }

    iconImageView.image = imageForIcon(withName: weatherData.icon)
}

Should the controller be in charge of this task? Maybe. Maybe not. But there is a more elegant solution.

If we adopt the Model-View-ViewModel pattern, the controller will no longer be responsible for data manipulation. What's more, the controller won't know about and have direct access to the model object. It will receive a view model from the root view controller and use that view model to populate its view. That is the task it was designed for, controlling a view.

Week View Controller

The week view controller suffers from the same problems. It keeps a reference to the array of WeatherDayData objects and uses it to populate its table view.

WeekViewController.swift

var week: [WeatherDayData]? {
    didSet {
        updateView()
    }
}

In the tableView(_:cellForRowAt:) method, the controller accesses the WeatherDayData object that corresponds with the index path and uses it to populate a table view cell. The raw values of the model object are transformed and formatted before they are displayed to the user.

WeekViewController.swift

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    guard let cell = tableView.dequeueReusableCell(withIdentifier: WeatherDayTableViewCell.reuseIdentifier, for: indexPath) as? WeatherDayTableViewCell else {
        fatalError("Unable to Dequeue Weather Day Table View Cell")
    }

    if let week = week {
        // Fetch Weather Data
        let weatherData = week[indexPath.row]

        var windSpeed = weatherData.windSpeed
        var temperatureMin = weatherData.temperatureMin
        var temperatureMax = weatherData.temperatureMax

        if UserDefaults.temperatureNotation != .fahrenheit {
            temperatureMin = temperatureMin.toCelcius
            temperatureMax = temperatureMax.toCelcius
        }

        // Configure Cell
        cell.dayLabel.text = dayFormatter.string(from: weatherData.time)
        cell.dateLabel.text = dateFormatter.string(from: weatherData.time)

        let min = String(format: "%.0f°", temperatureMin)
        let max = String(format: "%.0f°", temperatureMax)

        cell.temperatureLabel.text = "\(min) - \(max)"

        if UserDefaults.unitsNotation != .imperial {
            windSpeed = windSpeed.toKPH
            cell.windSpeedLabel.text = String(format: "%.f KPH", windSpeed)
        } else {
            cell.windSpeedLabel.text = String(format: "%.f MPH", windSpeed)
        }

        cell.iconImageView.image = imageForIcon(withName: weatherData.icon)
    }

    return cell
}

The tableView(_:cellForRowAt:) method is sprinkled with a handful of if statements. The if statements ensure that the raw values of the model object are formatted correctly, based on the user's preferences.

The Model-View-Controller pattern has a few other consequences. The week view controller has a couple of properties of type DateFormatter to format the raw values of the model object. If we use the Model-View-ViewModel pattern, we can clean this up too. Whenever I see a DateFormatter property in a controller, I know it is time for some refactoring.

Locations View Controller

Later in this series, we focus on the locations view controller. I will show you how user interaction is handled by the Model-View-ViewModel pattern. That is a bit more complicated. However, once you understand the ins and outs of the Model-View-ViewModel pattern, this won't be difficult to understand. I promise you that the result is elegant, readable, and maintainable.

Settings View Controller

There doesn't seem to be anything wrong with the settings view controller. It is true that it doesn't look too bad, but I assure you that it will look a lot better after we have given the settings view controller a facelift using protocols and MVVM.

What's Next

In the next episodes, you create your very first view model. We start with the view model for the day view controller.

Resources
Next Episode "A Quick Recap"