Now that you have an idea of the ins and outs of Cloudy, I’d like to take a few minutes to highlight some of Cloudy’s issues. Keep in mind that Cloudy is a small project. The problems we’re going to fix with the Model-View-ViewModel pattern are less apparent, which is why I’d like to highlight them before we fix them.
We start with the day view controller. The first thing to point out is that the view 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, when we adopt the Model-View-ViewModel pattern, this will change.
DayViewController.swift
1
var
now
:
WeatherData
?
{
2
didSet
{
3
updateView
()
4
}
5
}
The second and most important problem is the implementation of the updateWeatherDataContainer(withWeatherData:)
method. This is another pattern that’s typical for the Model-View-Controller pattern. The raw values of the model data are transformed and formatted before they’re displayed to the user.
DayViewController.swift
1
private
func
updateWeatherDataContainer
(
withWeatherData
weatherData
:
\
2
WeatherData
)
{
3
weatherDataContainer
.
isHidden
=
false
4
5
var
windSpeed
=
weatherData
.
windSpeed
6
var
temperature
=
weatherData
.
temperature
7
8
let
dateFormatter
=
DateFormatter
()
9
dateFormatter
.
dateFormat
=
"EEE, MMMM d"
10
dateLabel
.
text
=
dateFormatter
.
string
(
from
:
weatherData
.
time
)
11
12
let
timeFormatter
=
DateFormatter
()
13
14
if
UserDefaults
.
timeNotation
()
==
.
twelveHour
{
15
timeFormatter
.
dateFormat
=
"hh:mm a"
16
}
else
{
17
timeFormatter
.
dateFormat
=
"HH:mm"
18
}
19
20
timeLabel
.
text
=
timeFormatter
.
string
(
from
:
weatherData
.
time
)
21
22
descriptionLabel
.
text
=
weatherData
.
summary
23
24
if
UserDefaults
.
temperatureNotation
()
!=
.
fahrenheit
{
25
temperature
=
temperature
.
toCelcius
()
26
temperatureLabel
.
text
=
String
(
format
:
"%.1f °C"
,
temperatur
\
27
e
)
28
}
else
{
29
temperatureLabel
.
text
=
String
(
format
:
"%.1f °F"
,
temperatur
\
30
e
)
31
}
32
33
if
UserDefaults
.
unitsNotation
()
!=
.
imperial
{
34
windSpeed
=
windSpeed
.
toKPH
()
35
windSpeedLabel
.
text
=
String
(
format
:
"%.f KPH"
,
windSpeed
)
36
}
else
{
37
windSpeedLabel
.
text
=
String
(
format
:
"%.f MPH"
,
windSpeed
)
38
}
39
40
iconImageView
.
image
=
imageForIcon
(
withName
:
weatherData
.
icon
)
41
}
Should the view controller be in charge of this task? Maybe. Maybe not. But is there a more elegant solution? Absolutely.
If we adopt the Model-View-ViewModel pattern, the view controller will no longer be responsible for data manipulation. Moreover, the view controller won’t know about and have direct access to the model. It will receive a view model from the root view controller and use the view model to populate its view. That’s the task it was designed for, controlling a view.
The week view controller suffers from the same problems. It keeps a strong reference to the array of WeatherDayData
objects and uses them to populate the table view.
WeekViewController.swift
1
var
week
:
[
WeatherDayData
]?
{
2
didSet
{
3
updateView
()
4
}
5
}
In the tableView(cellForRowAt:)
method, a WeatherDayData
instance is fetched from the array and it’s used to populate a table view cell. The raw values of the model data are transformed and formatted before they’re displayed to the user.
WeekViewController.swift
1
func
tableView
(
_
tableView
:
UITableView
,
cellForRowAt
indexPath
:
Ind
\
2
exPath
)
->
UITableViewCell
{
3
guard
let
cell
=
tableView
.
dequeueReusableCell
(
withIdentifier
:
W
\
4
eatherDayTableViewCell
.
reuseIdentifier
,
for
:
indexPath
)
as
?
WeatherD
\
5
ayTableViewCell
else
{
fatalError
(
"Unexpected Table View Cell"
)
}
6
7
if
let
week
=
week
{
8
// Fetch Weather Data
9
let
weatherData
=
week
[
indexPath
.
row
]
10
11
var
windSpeed
=
weatherData
.
windSpeed
12
var
temperatureMin
=
weatherData
.
temperatureMin
13
var
temperatureMax
=
weatherData
.
temperatureMax
14
15
if
UserDefaults
.
temperatureNotation
()
!=
.
fahrenheit
{
16
temperatureMin
=
temperatureMin
.
toCelcius
()
17
temperatureMax
=
temperatureMax
.
toCelcius
()
18
}
19
20
// Configure Cell
21
cell
.
dayLabel
.
text
=
dayFormatter
.
string
(
from
:
weatherData
.
t
\
22
ime
)
23
cell
.
dateLabel
.
text
=
dateFormatter
.
string
(
from
:
weatherData
\
24
.
time
)
25
26
let
min
=
String
(
format
:
"%.0f°"
,
temperatureMin
)
27
let
max
=
String
(
format
:
"%.0f°"
,
temperatureMax
)
28
29
cell
.
temperatureLabel
.
text
=
"
\(
min
)
-
\(
max
)
"
30
31
if
UserDefaults
.
unitsNotation
()
!=
.
imperial
{
32
windSpeed
=
windSpeed
.
toKPH
()
33
cell
.
windSpeedLabel
.
text
=
String
(
format
:
"%.f KPH"
,
win
\
34
dSpeed
)
35
}
else
{
36
cell
.
windSpeedLabel
.
text
=
String
(
format
:
"%.f MPH"
,
win
\
37
dSpeed
)
38
}
39
40
cell
.
iconImageView
.
image
=
imageForIcon
(
withName
:
weatherDat
\
41
a
.
icon
)
42
}
43
44
return
cell
45
}
We also see several if
statements to make sure the raw values 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 model data that’s displayed in the table view. If we use the Model-View-ViewModel pattern, we can clean this up too. Whenever I see a DateFormatter
property in a view controller, I know it’s time for some refactoring.
Later in this book, we focus on the locations view controller. It will show you how user interaction is handled by the Model-View-ViewModel pattern. This 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 pure elegance.
There doesn’t seem to be anything wrong with the settings view controller. It’s true that it doesn’t look too bad, but I assure you that it’ll look a lot better after we’ve given the settings view controller a facelift using protocols and MVVM.
In the next chapters, you create your very first view model. We start with the view model for the day view controller.