MVVM in SwiftUI - Mohammad Azam
MVVM in SwiftUI - Mohammad Azam
Mohammad Azam is a veteran developer who has been professionally writing software
for more than a decade. Azam has worked as a lead mobile developer for many fortune
500 companies including Valic, AIG, Dell, Baker Hughes and Blinds.com. Azam is also
a top Udemy and LinkedIn instructor with more than 40K students. At present Azam is a
lead instructor at DigitalCrafts, where he teaches software development.
Azam is also an international speaker and has been professionally speaking since
2006. In his spare time Azam likes to exercise and plan his next adventure to the
unknown corners of the world.
Twitter: https://twitter.com/azamsharp
Udemy: https://www.udemy.com/user/mohammad-azam-2/
3
The term MVVM is used fluently in the Microsoft community. MVVM stands for Model
View ViewModel and it is one of the many design patterns for creating software. The
real name of MVVM is Presentation Model, which is inspired from Application Model in
Smalltalk.
Design Patterns are platform independent. This means you can use design patterns
with any language.
The three different components of MVVM are Model, View and ViewModel. Let’s take a
closer look at the different components of MVVM.
Model
The model represents the application domain. Models are classes in your code, which
identify business objects. Consider implementing an application for a banking system.
The domain objects in the banking system might consists of the following models:
Account
Customer
Transaction
Bank
AccountType
TransactionType
Each model represents a certain aspect of the business domain. A Customer object
represents a customer. This may include the customer’s name, address, ssn etc. The
Account object represents the customer’s account, which may include attributes like
account number, balance, type, status, interest rate etc.
Model may also contain business rules, which are based on the domain of the
application. For a banking applications these rules may include the following:
4
View
The view represents the user interface in the MVVM design pattern. This includes
webpages, screens of mobile devices, smart watches and even console/terminal
screens. Anything that can be visually represented is a view. A view is the primary way
a user can interact with your app i.e (pressing a button, scrolling, drag and drop etc).
ViewModel
The primary role of a view model is to provide data to the view. A view model consists of
properties which bind to the controls on the screen. Binding, simply means that data
from a particular property of the view model will be displayed on a particular user
interface element.
Binding can be bi-directional, meaning that the data from the view model is displayed on
the view and if view changes the data then it is automatically updated in the view model.
The whole idea behind binding is to keep the user interface (view) in-sync with the view
model.
At this point, you might be thinking that why use a view model, why not simply bind the
model to the view. Although you can do that, it is not advisable. Model contains the
business rules, validations and domain logic. Model may even contain tons of other
properties, which may make no sense to the view.
5
Consider a simple example of change password screen. The view is shown in Figure 1.
If you use a model to represent the change password screen, then you may end up with
the implementation shown in Listing 1.
1. struct User {
2. let username: String
3. let password: String
4. let confirmPassword: String
5. }
From a strictly technical point of view, the User model will fulfil your needs and your
application will still work as expected. The problem will arise when the view changes
and you need to accommodate new features. A change in the view will cause a change
in your business model. Let’s say we want to add an option to enable Captcha for our
password change screen. Our view will update as shown in Figure 2.
6
Now, we need to update our model to accommodate the captcha. This change is
reflected in Listing 2.
1. struct User {
2. let username: String
3. let password: String
4. let confirmPassword: String
5. let isRobot: Bool
6. }
As you can see, the user interface behavior is slowly creeping into our business models.
Soon it will have properties to represent the show/hide status of a particular view. The
whole point of the model is to deal with business rules, not the view.
This is the main reason why view models exists. View models represent the data and
the structure behind the view. Each view element can be binded to a property of the
view model. This means if the view updates the property, it is automatically updated in
the view model and vice versa. Listing 3 shows the implementation of
ChangePasswordViewModel, which is the view model behind the change password
screen.
1. struct ChangePasswordViewModel {
2. let password: String
3. let confirmPassword: String
4. let isRobot: Bool
5. }
Now that you have the basic information about the MVVM Design Pattern, let's take a
look at different ways view models can be structured in an application.
8
When building mobile applications, quite often you need to display a list of items. This
means you will have an array of objects, which you want to present on the view. There
are a number of ways to structure your view models to accommodate the need to
display a list.
You can always send an array of view models back to the view, but we found that
creating a parent view model which contains a list of view models is a more flexible
solution.
Consider a scenario, where you are building an app which is responsible for displaying
list of dishes. The dishes view is shown in Figure 3.
1. import UIKit
2.
3. struct DishListViewModel {
4. var dishes = [DishViewModel]()
5. }
6.
7. struct DishViewModel {
8.
9. let name: String
10. let course: String
11. let price: Double
12.
13. }
The DishListViewModel represents the entire view, which is responsible for displaying
dishes on the screen. DishListViewModel contains a nested list, represented by
DishViewModel objects. The benefit of this approach is that it gives you the flexibility to
add additional attributes/properties to the view model. For example, maybe you want to
add search functionality to your dishes screen. You can update the view to add a search
bar and update the view model to capture the search term as shown in Listing 5.
1. struct DishListViewModel {
2. var searchTerm: String = ""
3. var dishes = [DishViewModel]()
4. }
5.
6. struct DishViewModel {
7.
8. let name: String
9. let course: String
10. let price: Double
11.
12. }
By adding a parent view model, you add the flexibility to accommodate future changes
to the view much more easily.
As explained in the previous section, a parent view model can also accommodate
multiple child view models. It all depends on the user interface of the application. Take a
look at the Figure 4 which shows a Stocks app interface.
The stocks app displays two different types of information. It displays a list of stocks and
top news. A single parent view model can represent the whole screen. The parent view
model can be divided into multiple nested view models representing stocks and top
news articles. One possible implementation of parent view model is shown in Listing 6.
1. struct HomeViewModel {
2. var stocks = [StockViewModel]()
3. var articles = [ArticleViewModel]()
4. }
5.
6. struct StockViewModel {
7. let symbol: String
8. let price: Double
9. let company: String
10. let change: Double
11. }
12.
13. struct ArticleViewModel {
14. let title: String
15. let publication: String
16. let imageURL: String
17. }
The HomeViewModel represent the main model which controls the entire view. It is
composed of two child view models, stocks and articles respectively. The
HomeViewModel will be responsible for populating the child view models. This can be
done by calling a webservice and fetching data from an API or populating it from the
database.
A better organized and structured view model can help to populate the view more
efficiently. In the next section, we are going to take a look at how view models can be
used to validate user’s input and provide feedback.
12
Basic Validation
The first step is to build the user interface for the app. There are countless ways to
implement a registration view. We have used the Form view in SwiftUI to implement the
registration view. The implementation of the view is shown in Listing 6.1.
1. import SwiftUI
2.
3. struct ContentView: View {
4.
5. @State private var firstname: String = ""
6. @State private var lastname: String = ""
7. @State private var username: String = ""
8. @State private var password: String = ""
9.
10.
11. var body: some View {
12. NavigationView {
13.
14. Form {
15. VStack(spacing: 10) {
16. TextField("First name", text: $firstname)
17.
.textFieldStyle(RoundedBorderTextFieldStyle())
18. TextField("Last name", text: $lastname)
19.
.textFieldStyle(RoundedBorderTextFieldStyle())
20. TextField("Username", text: $username)
21.
.textFieldStyle(RoundedBorderTextFieldStyle())
22. SecureField("Password", text: $password)
23.
.textFieldStyle(RoundedBorderTextFieldStyle())
13
24.
25. Button("Register") {
26.
27.
28. }.padding(10)
29. .background(Color.blue)
30. .cornerRadius(6)
31. .foregroundColor(Color.white)
32.
33.
34.
35. }
36.
37. }
38.
39. .navigationBarTitle("Registration")
40.
41. }
42.
43.
44. }
45. }
At present our Register button does not perform any validation. We can write a very
basic validation by checking the value of the properties as shown in Listing 6.2.
15
As you can see, it works but it is not a good solution. First of all, we are managing
multiple independent state variables, which can be replaced by a single view model.
The second reason is that our registration view is responsible for validating the user
interface. As the complexity of the user interface grows, so does the validation code. In
the next section we will look into building a custom validation engine, which will be
responsible for performing validation for our view model.
The first step is to get rid of individual properties and replace them with a single view
model. We have implemented a view model called RegistrationViewModel which
represents the entire screen. The implementation is shown in Listing 6.3.
17
1. import Foundation
2.
3. class RegistrationViewModel: ValidationBase {
4. var firstname: String = ""
5. var lastname: String = ""
6. var username: String = ""
7. var password: String = ""
8. }
19. Button("Register") {
20.
21.
22.
23. }.padding(10)
24. .background(Color.blue)
25. .cornerRadius(6)
26. .foregroundColor(Color.white)
27.
28.
29. }
30. }
31. .navigationBarTitle("Registration")
32.
33. }
34. }
35. }
One thing to note is that we have decorated the view model instance with
@ObservableObject. This means that RegistrationViewModel can publish events
which can be observed by the view or anyone who wants to subscribe to it. To make our
code modular and maintainable, we have created a ValidationBase class which
provides base implementation of broken rules. The implementation of ValidationBase
class is shown in Listing 6.5.
1. struct BrokenRule {
2. let id = UUID()
19
The name represents the property, which failed the validation and the message
represents a custom message to be displayed to the user.
We have also implemented a Validator protocol, which will be used by view models
interested in providing validation behavior. The Validator protocol is shown in Listing
6.7.
The validate function first clears out any existing broken rules and then performs
validation on the individual properties of the view model. If the validation fails, a broken
rule is added to a list of broken rules.
In the last section, we implemented the validate function, responsible for performing
validation on the properties of the view model. In this section we will display the broken
rules on the view.
Instead of polluting the registration view (ContentView), we will create a brand new view
called BrokenRulesView. BrokenRulesView will take a list of broken rules as an
21
argument and then display them on the view. The implementation of BrokenRulesView
is shown in Listing 6.9.
1. import SwiftUI
2.
3. struct BrokenRulesView: View {
4.
5. let brokenRules: [BrokenRule]
6.
7. var body: some View {
8. List(brokenRules, id: \.id) { rule in
9. Text(rule.message)
10. }
11. }
12. }
13.
14. struct BrokenRulesView_Previews: PreviewProvider {
15. static var previews: some View {
16. BrokenRulesView(brokenRules: [])
17. }
18. }
The List view is used to iterate over the array of broken rules and then display them
using a Text view. Listing 6.10 shows how to use the BrokenRulesView in registration
view (ContentView) to display the validation errors.
1. Form {
2. ...
3.
4. BrokenRulesView(brokenRules:
registrationVM.brokenRules)
5.
6. }
7. }
Run the application and click the register button without filling out the TextFields. The
validate function gets triggered and passes down the broken rules to BrokenRulesView
where finally it gets displayed as shown in Figure 4.3.
This approach also gives you the flexibility to customize the display of validation errors
by changing the user interface through the modification of BrokenRulesView. Although
this approach works, we can definitely make it better. Swift 5.1 introduced property
wrappers which allows custom code to be triggered when evaluating properties.
In the next chapter you will learn how to use the ValidatedPropertyKit framework to
perform validation in SwiftUI. ValidatedPropertyKit framework performs validation by
decorating the properties with property wrappers.
23
Integrating ValidatedPropertyKit
Click on the Next button and complete the whole process. Once the dialog closes your
dependency to ValidatedPropertyKit will be successfully added to the project. Next we
will decorate our view models with property wrappers which will enable validation.
1. import Foundation
2. import ValidatedPropertyKit
3.
4. class RegistrationViewModel: ObservableObject {
5.
6. @Validated(.nonEmpty)
7. var firstname: String? = ""
8.
9. @Validated(.nonEmpty)
10. var lastname: String? = ""
11.
12. @Validated(.nonEmpty)
13. var username: String? = ""
14.
15. @Validated(.nonEmpty)
16. var password: String? = ""
17.
18. }
Listing 6.11: RegistrationViewModel decorated with property wrappers
Please note that we have declared properties as optionals and initialized them as empty
strings as ValidatedPropertyKit will only validate not nil values.
You can also create your own custom validation wrappers for situations that are not
covered in the default ValidatedPropertyKit.
The next step is to implement the validate function which will be responsible for
evaluating the properties. The implementation is shown in Listing 6.12.
Inside the validate function we create a rules array, which contains the name of the
property and the validation error associated with the property.
26
Next, we iterate through the rules and if we find any failure reason associated with the
property we add that as a broken rule to the brokenRules array. The brokenRules is
also marked with @Published property wrapper, which means that it will publish an
event as soon as it is updated.
The final step is to display the broken rules on the user interface. This is exactly the
same as before, we will be calling the validate function on the registrationVM and
then using the BrokenRulesView to display the errors. The implementation is shown in
Listing 6.13.
25. .cornerRadius(6)
26. .foregroundColor(Color.white)
27.
28.
29. BrokenRulesView(brokenRules:
registrationVM.brokenRules)
30.
31.
32. }
33. }
34. .navigationBarTitle("Registration")
35.
36. }
37. }
38. }
For those of you paying attention, you will notice that we used a bound function when
binding the values to the TextField view. The reason is that TextField binds to
Binding<String> but not Binding<String?>. The bound extension allows us to
unwrap the optional, which can then be used to bind to the views on the user interface.
The implementation of the bound function is available as downloadable source code.
As you can see in Figure 4.5, validation is fired and the default error messages are
displayed on the screen. This will work for some situations but it would be great if we
can customize the error messages. In the next section, you will learn how to write a
custom validation rule, which can provide a custom error message.
In order to customize or add a new validation rule you will need to extend the Validation
structure provided by ValidatedPropertyKit. Add a new file
“Validation+Extension.swift” to your existing project and add a new static property
called required. The implementation is shown in Listing 6.14
29
1. import Foundation
2. import ValidatedPropertyKit
3.
4. extension Validation where Value == String {
5.
6. static var required: Validation {
7. return .init { value in
8. value.isEmpty ? .failure("\(value) cannot be empty")
: .success(())
9. }
10. }
11.
12. }
The required property is our custom property, which will perform validation on String
values. If the value is empty, failure closure is triggered with the error message,
otherwise success closure is triggered.
Next, update your RegistrationViewModel and use the new required validation rule. This
is shown in Listing 6.15.
Run your app again and click on the register button without filling out the form. Figure
4.6 shows the result.
30
We can even take one step further and provide error messages right inside the property
wrapper. For this to work we will update the custom required validator from property to
a function. The implementation is shown in Listing 6.16.
Now, you can update your RegistrationViewModel to utilize the new required
function. This is shown in Listing 6.17.
In this chapter you learned how to use ValidatedPropertyKit to validate your view model
properties. ValidatedPropertyKit is an amazing framework, which uses the power of
Swift 5.1 property wrappers to allow developers to add validation to their apps.
References
1) ValidatedPropertyKit GitHub
2) NSScreencast ValidatedPropertyKit Demo Project
3) Implementation of bound function on StackOverFlow
33
When building applications using MVVM design pattern it is common to put a lot of
functionality into the view models. A view model should not be responsible for making a
network request. However, a view model can take help from a networking client to
perform the request. Figure 5 shows the flow.
Figure 5: Roundtrip data flow between the view and the server
As shown in Figure 5, an event reaches the view model triggered from the view. The
view model forwards the request to the HTTP client, which performs the actual request.
The response is returned from the server to the HTTP client where it is passed to the
34
view model. The View model processes the model objects and returns the view model
objects to the view. Finally, the view uses the view model to display the data on the
screen.
For this chapter, we have built a very simple JSON API using Node and Express. The
API is hosted on Glitch server at https://silicon-rhinoceros.glitch.me and it
returns hard-coded data.
[{"symbol":"GOOG","description":"Google Innovation
Media","price":1080,"change":"-0.24"},{"symbol":"MSFT","description":
"Microsoft
Cooporation","price":60,"change":"+3.45"},{"symbol":"FB","description
":"Facebook Social
Media","price":220,"change":"-1.56"},{"symbol":"AMAZON","description"
:"Amazon Everything Store ","price":400,"change":"+6.56"}]
Listing 7: JSON response from stocks endpoint
The response consists of an array of stocks. Each stock contains attributes including
symbol, description, price and change. In the next section we are going to implement
HTTPClient, which will be responsible for fetching the response from the JSON API.
The main purpose of HTTPClient is to send a request to an endpoint and then process
JSON response. In Listing 8 you can see the implementation of HTTPClient, which is
responsible for returning list of stocks.
10.
11. guard let url = URL(string: urlString) else {
12. completion(.failure(.invalidURL))
13. return
14. }
15.
16. URLSession.shared.dataTask(with: url) { data,
response, error in
17.
18. guard let data = data, error == nil else {
19. completion(.failure(.unknownError))
20. return
21. }
22.
23. let stocks = try?
JSONDecoder().decode([Stock].self, from: data)
24. stocks == nil ?
completion(.failure(.decodingError)) :
completion(.success(stocks!))
25.
26. }.resume()
27. }
28.
29. }
We have used the Result type feature of Swift 5 in our completion handler. This allows
us to easily create handlers for success and error callbacks. One important point to note
about HTTPClient is that it returns model objects and not view models. The model is
implemented in Listing 9.
The Stock model conforms to the Decodable protocol, making it easier to get populated
by JSON response.
The property names of Stock model does not have to be the same as the JSON
response. You can always use the CodingKey protocol to dictate your own custom
mapping.
The StockViewModel is responsible for representing each stock in the view. The
implementation is shown in Listing 10.
1. struct StockViewModel {
2.
3. let stock: Stock
4.
5. init(stock: Stock) {
6. self.stock = stock
7. }
8.
9. var symbol: String {
10. return self.stock.symbol.uppercased()
11. }
12.
13. var description: String {
14. return self.stock.description
15. }
16.
17. var price: String {
18. return String(format: "%.2f",self.stock.price)
19. }
20.
21. var change: String {
22. return self.stock.change
23. }
24.
25. }
As mentioned in earlier chapters, it is a good idea to create a parent view model which
represents the entire screen. The implementation of StockListViewModel is shown in
Listing 11.
37
1. class StockListViewModel {
2.
3. var stocks = [StockViewModel]()
4.
5. func fetchAllStocks() {
6.
7. HTTPClient().getAllStocks(urlString:
"https://silicon-rhinoceros.glitch.me/stocks") { result in
8. DispatchQueue.main.async {
9. switch result {
10. case .success(let stocks):
11. self.stocks =
stocks.map(StockViewModel.init)
12. case .failure(let error):
13. print(error.localizedDescription)
14.
15. }
16. }
17.
18. }
19.
20. }
21. }
The whole point of this flow is to build layers, where each layer is responsible for a
certain aspect of the application. This will help us to keep the code cleaner and
maintainable for future changes.
Before integrating MVVM design pattern into SwiftUI apps, it would be a good idea to
learn about the SwiftUI framework. In the next few chapters you will learn about SwiftUI,
Apple’s new declarative framework. You will also learn about state management in
SwiftUI, which is an integral subject when building SwiftUI applications.
38
Hello SwiftUI
At WWDC 2019 Apple announced SwiftUI framework. SwiftUI is a declarative
framework for building user interfaces for all Apple devices. This means that instead of
using Storyboards or dynamically creating your controls, you can use SwiftUI’s fluent
declarative syntax to build iOS interfaces.
If you have ever worked with React Native or Flutter then you will find a lot of similarities
in SwiftUI framework. One of the main features of SwiftUI framework is Xcode previews,
which enables the interface to update in real-time as you implement it. This feature is
similar to Hot Reload in React Native and Flutter.
Getting Started
In order to build SwiftUI apps you need to have Xcode 11 Beta installed. Although
Xcode previews feature is only available on macOS Catalina, we can still see live
preview feature using Playgrounds on macOS Mojave. For the most part of this book,
we will be using macOS Catalina.
Apart from this small section this book explicitly uses macOS Catalina. But it would be
beneficial to see how you can use macOS Mojave to run SwiftUI applications. In order
to get a preview of our UI we will be using Xcode 11 Playgrounds. Check out the
implementation in Listing 12 which shows how to preview SwiftUI.
1. import UIKit
2. import PlaygroundSupport
3. import SwiftUI
4.
5. struct ContentView: View {
6. var body: some View {
7. Text("Hello SwiftUI from macOS Mojave")
8. }
9. }
10.
11. let contentView = ContentView()
12. PlaygroundPage.current.liveView =
39
UIHostingController(rootView: contentView)
The main advantage of using Xcode 11 on macOS Catalina is the power of Xcode
previews, which allows us to visualize user interface instantly. From this point
onwards we will be using macOS Catalina for the remainder of the book.
Launch Xcode 11 and create a new Single View Application. Make sure to select
“SwiftUI” as shown in Figure 6.
40
Press “Next” and choose the location to create the project. Xcode 11 will create the project and
open up the editor showing multiple panes. Figure 7 explains the purpose of each pane.
41
One thing you will immediately notice is that there are no view controllers. Although you
can use view controllers in SwiftUI application, it is recommended that you use MVVM
Design Pattern instead. In this book we will not cover MVVM Design Pattern but you
can definitely check out my course to learn more.
Let’s try to dissect the code in Listing 13 and understand how it works.
The ContentView is the name for your view. It conforms to the protocol View, which
only has a single requirement to provide implementation of the body property. The body
property returns some kind of View. The keyword some indicates that it is an opaque
type being returned. This means that even though you are returning a type of View,
42
compiler will know the exact type. Inside the body property we return a Text view which
is responsible for displaying text on the screen. The output is shown in Figure 8.
Go ahead and change the “Hello World” text in Text view to something different. You
will notice that the Xcode preview automatically updates and reflect your changes.
Xcode previews are amazing and going to help speed up the development of your
interfaces drastically!
43
The next piece of code is used to create Xcode previews as shown in Listing 14.
1. #if DEBUG
2. struct ContentView_Previews : PreviewProvider {
3. static var previews: some View {
4. ContentView()
5. }
6. }
7. #endif
Please note that Xcode previews construct the actual view by calling the view in the
previews property. It is not an emulation of the view, it is the actual view.
Going back to the implementation of ContentView. If you try to add another Text view
then it will give you an error indicating that this is not a valid operation as shown in
Listing 15.
The reason is that two or more elements cannot be at the same parent level. In order to
accommodate multiple childs inside a view we will take help from Stack. Stacks are
layout container views which means that their primary purpose is to help with the layout
of the view. They are also container views, which means they can contain other child
views. In SwiftUI framework, stacks comes in three different flavours. This includes
ZStack, HStack and VStack.
44
HStack is also known as horizontal stack. The main purpose of HStack is to arrange
elements horizontally. Figure 9 shows the stacking order of HStack.
VStack or vertical stack is used to stack items vertically. Figure 10 shows the stacking
order of VStack.
VIEW 1
VIEW 2
VIEW 3
Finally, ZStack is used to stack items behind or infront of each other. This is
demonstrated in Figure 11.
Figure 11: ZStack is used to stack views in front or behind each other
Now, that you have a basic understanding of stacks let’s go ahead and use it in our
SwiftUI application. Listing 16 shows how we have stacked two Text views on top of
each other.
45
You can also command click on a particular view and embed it inside different kinds of
controls as shown in Figure 13.
46
Similarly, you can use HStack to embed elements in horizontal direction as shown in
Listing 17.
ZStack is a little different because it allows to layer items on top of each other. Take a
look at Listing 18, which shows how to add three Button views stacked on top of each
other.
12.
13. }.padding(100)
14. .background(Color.blue)
15. .offset(y: 50)
16.
17. Button("VIEW 3") {
18.
19. }.padding(100)
20. .background(Color.yellow)
21. .offset(y: 100)
22.
23.
24. }
25.
26. }
27. }
Stacks serves as an essential layout view in SwiftUI and It allows to structure and align
other parts of your app in a seemingly easy way. In the next chapter you will learn about
List view, which is used for creating and displaying multiple items in a view. You will
also realize how stacks become an important and integrate layout containers for
designing and structuring your iOS applications.
50
Similar to SwiftUI Stack, List serves as a container view, which means it can have child
views. Having worked with UIKit’s UITableView control, you will find working with List a
great experience. Let’s start our journey by displaying a list of numbers in a List.
Wow! We were able to achieve all that with just a few lines of code without having to
setup any delegates or data sources.
The List view requires two arguments, which includes the collection and the keypath to
uniquely identify them. For the first argument, we passed a closed range and for the
second argument we passed .\self to identify each value based on their hash. In the
next example let’s check out how we can create a List based on custom objects.
Custom model objects are used to represent the domain of your application. In this
example we will populate our List with custom objects.
52
The first task is to create a class or struct to represent our custom object. Let’s create a
class called Country which will represent a particular country in the world as shown in
Listing 20.
1. import Foundation
2.
3. struct Country {
4.
5. let flag: String
6. let continent: String
7. let name: String
8.
9. }
Listing 20: Implementing of Country model
Since, we are not connected to any database and not consuming any web service we
will have to come up with country instances ourselves. Inside the Country struct we
have added a static function called “all” which will be responsible for returning
hard-coded Country objects as shown in Listing 21.
extension Country {
return [
"USA"),
Country(flag: "
Country(flag: " "",, continent : "Asia", name: "Pakistan"),
continent: "North America", name:
Country(flag: "
", continent: "South America", name:
"Brazil"),
]
Country(flag: "
", continent: "Asia", name: "Chine")
}
Now, let’s jump into ContentView and see how we can iterate through all the countries
and display them in a List control. The implementation is shown in Listing 22.
One important thing to note is that we used the key path “name” to uniquely identify
each country. You can replace “.\name” with “.\self” but then you will have to
make sure that your country objects confirms to Hashable protocol.
Inside the List we used the HStack to arrange our items. We also used the Spacer view
to add flexible space between the views. Figure 17 shows the List view populated with
countries.
54
Sweet!
Although this works great and solves our needs but what if we needed to display a
header before the List. This header can be anything but for the sake of simplicity we will
display an image for the header. The problem is that List itself cannot accommodate
headers and footers on its own.
But we can use a ForEach inside the List, which will help us create views dynamically.
ForEach computes the views on demand based on the collection. The implementation is
shown in Listing 23.
4.
5. var body: some View {
6.
7. List {
8.
9. Image("un").resizable()
10. .frame(height: 300)
11.
12. ForEach(self.countries, id: \.name) { country in
13.
14. HStack {
15. Text(country.flag)
16. Text(country.name)
17. Spacer()
18. Text(country.continent)
19. }
20. }
21.
22. }
23. }
24. }
This is a great starting point for our app. Unfortunately most of the apps don’t end on
one single screen. It would be great if we can tap on the cell and see more details about
the country. Let’s do that in the next section.
57
Adding Navigation
As you can see we added NavigationView as the root view of our app. We also added
the title for your view by using .navigationBarTitle.
58
If you are not interested in large titles then you can pass the display mode and set the
value inline as shown in Listing 25.
Command click on the HStack and select “Extract Subview” option. Extract subview is
going to extract a subview and put it underneath the current view. Unfortunately, our
application will not compile and return a lot of errors. The reason is simple! The
extracted view does not have access to the country object. This can be easily fixed by
introducing the country property on the extracted subview. We will also rename our
extracted view to “CountryView” as shown in Listing 26.
34. Text(country.continent)
35. }
36. }
37. }
Next step is to add navigation to our cells. Simply wrap the HStack with NavigationLink
so we can tap on the cell and proceed to the destination screen. Listing 27 shows the
implementation of NavigationLink.
Go ahead and run the application and tap on the cell. You will notice that a push
navigation takes place and shows you a destination view with the name of the country.
This is great but we can make it even better. Instead of showing a Text view in the
destination, we can create a detail screen.
Add a new SwiftUI file to your project and name it “CountryDetailView”. The
implementation of the detail screen is shown in Listing 28.
1. import SwiftUI
2.
3. struct CountryDetailView: View {
4.
5. var country: Country
6.
7. var body: some View {
8. VStack {
9. Text(country.flag)
10. .font(.custom("Arial", size: 100))
11.
12. Text(country.name)
13. }
14. }
15. }
16.
17. #if DEBUG
18. struct CountryDetailView_Previews: PreviewProvider {
19. static var previews: some View {
20. CountryDetailView(country: Country(flag: " ",
continent: "Asia", name: "Pakistan"))
21. }
22. }
23. #endif
Listing 28: CountryDetailView
The CountryDetailView displays the flag of the country and the name of the country
using the Text view. In order to create an instance of CountryDetailView you need to
pass the country instance to the view. Listing 29 shows how to use the
CountryDetailView as a destination view from within the ContentView.
2.
3. var countries = Country.all()
4.
5. var body: some View {
6.
7. NavigationView {
8.
9. List {
10.
11. Image("un").resizable()
12. .frame(height: 300)
13.
14. ForEach(self.countries, id: \.name) { country in
15. NavigationLink(destination:
CountryDetailView(country: country)) {
16. CountryView(country: country)
17. }
18. }
19.
20. }
21. .navigationBarTitle("Countries", displayMode:
.inline)
22. }
23. }
24. }
Go ahead and run the application again! This time you will notice that when you tap on
a cell, you are taken the CountryDetailView which shows the flag of the country along
with the name of the country. Figure 20 shows the CountryDetailView as the destination
view.
63
Please note that under the hood SwiftUI does use NavigationController but all of that
complexity is hidden when using SwiftUI framework.
At present we have used controls which can display values on the view. But what about
interactive controls. Controls that changes and maintain states. In the next chapter we
are going to take a deep dive into the concept of data flow in SwiftUI applications.
64
Similar concepts are available in ReactJS and Flutter, where changing the state causes
render and build functions to fire respectively.
Let’s take a look at a simple example of state in SwiftUI in Listing 30. In this example
we will allow the user to toggle a switch. Based on the status of the switch the icon will
change between night and day.
🌞
12.
🌙
13. Text(self.isOn ? " " :
" ").font(.custom("Arial", size: 100))
14.
15. }
16.
17. }
18. }
Listing 30: Toggling State in SwiftUI
65
The important part in this example is the use of @State property wrapper on the isOn
boolean property. We have also marked it with private keyword indicating that this state
is a local/private state for the ContentView component.
The Toggle view takes in a Binding<Bool>, which is satisfied by passing the isOn
state property. When Toggle changes the state between on or off the state value gets
updated, rendering the view again.
Great!
Now that you have some basic understanding of state, let’s see how we can capture
multiple pieces of information using state.
66
The Register view is implemented in Listing 31. It includes all the basic elements for
registering a new user. To keep this example simple we are using basic SwiftUI
elements like TextField etc. In your actual application you can use the power of Form
view available in SwiftUI framework.
The state of the Register view is controlled by four independent variables. Each
variable captures a slice of the state. As the user types in the TextFields, the state
variables gets updated.
This mostly works great! But we have to deal with four independent variables, which
represent a single model. This can be simplified by introducing a view model which
represents the state of the view.
Although, you can use any design pattern to build your SwiftUI application but the
recommended pattern is Presentation Model, also known as MVVM. In MVVM design
pattern you will start by creating a view model which will be responsible for providing
data to the view and managing the state associated with the view.
1. struct RegistrationViewModel {
2.
3. var firstName: String = ""
4. var lastName: String = ""
5. var username: String = ""
6. var password: String = ""
7.
8. }
Listing 32: RegistrationViewModel
Now, we can update the Register view and utilize our newly created
RegistrationViewModel as shown in Listing 32.1.
68
1. import SwiftUI
2.
3. struct RegistrationViewModel {
4. var firstName: String = ""
5. var lastName: String = ""
6. var username: String = ""
7. var password: String = ""
8. }
9.
10.
11. struct Register: View {
12.
13. @State private var registrationVM: RegistrationViewModel
= RegistrationViewModel()
14.
15. var body: some View {
16.
17. NavigationView {
18.
19. VStack {
20. TextField("First Name", text:
self.$registrationVM.firstName)
21. TextField("Last Name", text:
self.$registrationVM.lastName)
22. TextField("User Name", text:
self.$registrationVM.username)
23. SecureField("Password", text:
self.$registrationVM.password)
24. Button("Register") {
25. // register the user
26. }
27.
28. }.padding()
29.
30. .navigationBarTitle("Register")
31. }
32.
33. }
34. }
As you can see our Register view is now much simpler and the
RegistrationViewModel is responsible for maintaining the state of the view. We have
69
removed the individual slices of state variables and replaced it with a single view model.
Now anytime a user updates any of the TextField, the view model gets updated
automatically. The MVVM design pattern allows to cleanly structure your SwiftUI
application and at the same time making it easier to write unit tests.
But what if you wanted to change the state of the parent from a child view. This is
performed by @Binding property wrapper, which is explained in the next section.
@Binding
A single view in SwiftUI may be composed of multiple child views. Sometimes you want
to allow the child to change the state of the parent view. Binding allows you to pass
state from the parent view to the child view. Once the child view alters the state, the
parent automatically gets the updated copy and re-renders the view. Let’s implement
the same day/night example as before but this time we will put the Toggle view into a
child view called “DayNightView”.
1. import SwiftUI
2.
3. struct DayNightView: View {
4.
5. @Binding var isOn: Bool
6.
7. var body: some View {
8. Toggle(isOn: $isOn) {
9. Text("")
10. }.labelsHidden()
11. }
12. }
13.
14. struct DayNightView_Previews: PreviewProvider {
15. static var previews: some View {
16. DayNightView(isOn: .constant(false))
17. }
18. }
The property wrapper @Binding indicates that the isOn property will be passed to the
DayNightView. Once the DayNightView changes the isOn property then the original
sender will be notified by re-rendering the view.
Assigning the isOn property will cause render to get called on the parent view
The main purpose of @Binding is to pass the state to a child view where it can be
updated. This gives child view(s) an opportunity to communicate with the parent and
update the parent.
Most of the apps fetch their data from an outside source, mainly using JSON Web API.
Once the data is downloaded it is populated in a DTO (Data Transfer Object) and later
mapped to the view models and then displayed on the screen.
One common issue with consuming asynchronous requests is to notify the user
interface that data has been downloaded so the view can display fresh data. SwiftUI
solves this problem by introducing Observable and Observed property wrappers.
Before jumping into Observable and Observed property wrappers we must find a way
to perform asynchronous requests to fetch data. For the sake of simplicity we are going
to make a fake request and get a list of posts in an asynchronous manner as shown in
Listing 35.
1. struct Post {
2. let id = UUID().uuidString
3. let title: String
4. let body: String
5. }
6.
7. class Webservice {
8.
9. func fetchPosts(completion: @escaping ([Post]) -> Void) {
10.
11. DispatchQueue.main.asyncAfter(deadline: .now() + 2.0)
{
12. // fetch from a web api and then populate the
Post array
13. let posts = [
14. Post(title: "Hello SwiftUI", body: "Learn to
create your first SwiftUI App!"),
15. Post(title: "Getting started with Combine",
body: "Introduce reactive programming using Combine framework")
16. ]
17.
18. completion(posts)
19. }
20.
21. }
22.
23.
24. }
72
The Webservice simply waits for 2 seconds and then sends hardcoded list of posts
back to the user in a completion handler.
The PostListViewModel represents the data, which will be displayed on the post listing
screen. The most important thing to notice is the use of ObservableObject protocol.
The ObservableObject protocol allows the class to publish events. The posts property
73
is also decorated with @Published property wrapper, which means it acts like a
publisher. When a value is assigned to the posts property, it publishes an event
indicating that it has been changed.
Finally, the PostListView uses the PostListViewModel to fetch and display the posts
in a view as implemented in Listing 37:
At present we have only discussed local state, which represents the state maintained
and available to a particular view. If you need to change the state of the view from
another view you can pass it as an argument and using the @Binding property wrapper.
This works great if you are passing the state between few views but quickly become a
74
hassle when several views are involved or when you need to pass the state to a deeply
nested view in the hierarchy.
In the next section we are going to look at global state, which can be accessible from
any SwiftUI view.
@EnvironmentObject
To keep the example simple we are going to create a class called UserSettings, which
will be shared between multiple views. We will implement three different views namely
Facebook, Twitter and TotalLikes. The Facebook and Twitter views will allow the user to
increment likes and TotalLikes view will be responsible for displaying the total likes.
1. import Foundation
2.
3. class UserSettings: ObservableObject {
4. @Published var likes: Int = 0
5. }
The UserSettings class is using the ObservableObject protocol, which means it can
publish events. The only property in UserSettings class is likes which is decorated
with @Published property wrapper indicating that it will act as a publisher and will notify
the subscribers when the value changes.
Before using UserSettings, we need to inject it into the parent view. Open
SceneDelegate.swift and implement the code shown in Listing 39.
Once injected in the ContentView, the UserSettings object will be available to the
ContentView and all the views inside ContentView. Next we will implement Facebook
and Twitter view as shown in Listing 40.
1. import SwiftUI
2.
3. struct Facebook: View {
4.
5. @EnvironmentObject var userSettings: UserSettings
6.
7. var body: some View {
8.
9. VStack {
10.
11.
12.
👍
Text("Facebook")
Button(" ") {
self.userSettings.likes += 1
13. }
14. }
15. }
16. }
17.
18. struct Twitter: View {
19.
20. @EnvironmentObject var userSettings: UserSettings
21.
22. var body: some View {
23.
24. VStack {
25.
26.
27.
👍
Text("Twitter")
Button(" ") {
self.userSettings.likes += 1
76
28. }
29. }
30. }
31. }
The updates to the global state will also cause the views to render again
automatically
The TotalLikes view is responsible for displaying the value of likes property.
Finally, the Twitter and Facebook views are used inside the ContentView as shown in
Figure 22.
77
Awesome!
You were able to share the state between multiple views by using global state in
SwiftUI. EnvironmentObject is idol when you want to share data with multiple views,
especially if those views are nested deep into the view hierarchy.
78
In the previous chapters you learned about the MVVM Design Pattern, SwiftUI and state
management in SwiftUI. Now it is time to look at how MVVM pattern can be integrated
with a SwiftUI application. In this chapter we will start with a very basic counter
application using SwiftUI and MVVM Design Pattern. The point of this chapter is to get
comfortable with different components of MVVM and how it fits with the SwiftUI
framework.
View
The view for our counter app is pretty simple. It consists of a single Text label and two
Button views. One button is to increment the counter and the other is to decrement the
counter. The complete code for the view is shown in Listing 41.
79
Next step is to create a model, which will be responsible for providing the updated
counter value.
The Counter model consists of separate functions for increment and decrement
operations. The value property represents the updated/current value of the counter. The
complete implementation of the Counter model is shown in Listing 42.
81
1. import Foundation
2.
3. struct Counter {
4.
5. var value: Int
6.
7. mutating func increment() {
8. value += 1
9. }
10.
11. mutating func decrement() {
12. value -= 1
13. }
14.
15.
16. }
If you want to add any business rules that needs to validate the counter value then you
can add those in the increment and decrement functions. The Counter model will be
used by the CounterViewModel, which will be responsible for updating the values on
the view.
Implementing CounterViewModel
1. import Foundation
2.
3. class CounterViewModel: ObservableObject {
4.
5. @Published var counter: Counter = Counter(value: 0)
6.
7. var value: Int {
82
8. return self.counter.value
9. }
10.
11. func increment() {
12. self.counter.increment()
13. }
14.
15. func decrement() {
16. self.counter.decrement()
17. }
18. }
The view has been updated to make use of the CounterViewModel as shown in Listing
44.
15. }
16. }
17. }
18. }
19. }
Once the user clicks the increment or decrement button, it updates the counter value in
CounterViewModel. Since the counter property is marked with @Published, it
publishes an event which is handled in the ContentView. Finally, the Text view
displays the updated value of the counter. The result is shown in Figure 24:
84
In this chapter, you learned how to implement MVVM design patterns in a simple
SwiftUI application. In the next chapter, we are going to combine everything we have
learned to build a real world Notes application using SwiftUI, MVVM and Networking.