iOS App Development
Creating an iOS App With SwiftUI, Combine, MVVM, and Protocols [Part 3]
In this tutorial, we will create a simple To-do list app, learn how to reuse views in Lists, implement interaction with the model and dynamic update of views. We will use the brand new frameworks, SwiftUI and Combine, released by Apple in 2019. We will use the MVVM pattern and the Protocol-Oriented Programming paradigm.
In this tutorial we use the latest version of Xcode (11.3.1) and macOS Catalina (10.15.4) for the moment of writing.
SwiftUI and Combine are brand new frameworks introduced on WWDC 2019 by Apple that dramatically change the way we develop iOS apps. These frameworks make our work faster and more convenient, help us write readable code and allow us to test code easier. SwiftUI provides a declarative way of creating interfaces that speed up the development, while Combine brings Functional Reactive programming (FRP) into iOS projects and helps in the architecture of iOS apps allowing to divide code into separate parts with the MVVM pattern.
In our project we will use Protocol-Oriented Programming that simplifies architecting iOS apps, helps us avoid a tight coupling of classes with one another, allows as to use all the power of structs and enums by conforming them to protocols and makes it easier to write unit tests.
Our structural design pattern will be MVVM, as one of the most convenient for the iOS development with SwiftUI and Combine. MVVM divides code into three parts: Model, View, and ViewModel.
This tutorial is a part of the series of tutorials. Although you can work only with this tutorial and learn how to develop apps with SwiftUI and Combine, using MVVM and Protocols, it’s advisable to check the previous parts:
- Getting Started with SwiftUI and Combine Using MVVM and Protocols for iOS
- Creating an iOS App With SwiftUI and Combine Using MVVM and Protocols
- Creating an iOS App With SwiftUI, Combine, MVVM, and Protocols [Part 3] (this tutorial)
- How to Create Your Own Framework for Your iOS App
- Using Core Data in Your SwiftUI App with Combine, MVVM and Protocols
If you don’t want to check the previous parts and prefer to start right away, you can download the complete code here.
What we will learn
By the end of this tutorial we will learn:
- How to reuse views in Lists;
- How views interact with model via ViewModel and vice versa;
- How views react on updates in models.
We will continue creating a simple To-do list app that uses SwiftUI and Combine. We will use the power of MVVM and Protocols. Let’s get started!
Link to an updated video version on YouTube: https://www.youtube.com/watch?v=KMA71yPNFq4&list=PLNzBC8jVdb__Wf-ojLhTP7FGj7wFhPc7N&index=3
Getting Started
Open the project developed in the previous parts (or download the app here and open it in Xcode). Make sure you have the following:
- TodoListView and TodoListViewModel. The view shows the list of all the Todos we have;
- NewTodoView and NewTodoViewModel. This view allows us to create a new Todo;
- DataManager and the Todo model. This our model and a simple data manager that stores all the data we have (for now it’s no a persistent storage, we’ll fix it in the next parts);
- KeyboardResponder to react on the keyboard appearing and disappearing to update our views when needed.
If you do not have any prior experience working with SwiftUI and Combine or you do not understand any part of this project, please take a look into the tutorial where we created this starter app from scratch. It will help you understand easier the following steps.
Rows
One thing that doesn’t seem to be immaculate is the rows in the list of Todos. For now it’s only Text but what if we decide (and we’re going to) to make the row more complicated. It’ll make the main view heavier and lower the readability of the code.
Let’s create a new SwiftUI View and call it TodoRow.swift. In the new struct, we’ll add a variable todo. We’ll change the body view to contain Text(todo.title). Also, we need to update TodoRow_Previews to pass a Todo into TodoRow. Finally, to make the row look like a row in the Canvas, we’ll add the .previewLayout modifier:
Now, open TodoListView and replace Text(todo.title) in the List with TodoRow(todo: todo):
You see that nothing changed, but it’s fine, it’s because our row consists only of Text that is the same. Now, we reuse TodoRow in the List and we can change its appearance in TodoRow.swift and touch nothing in TodoListView.swift. It’s really convenient, and now we can focus on the row and make it look better. After all, we’re developing a To-do list app, we probably need a checkmark on the left side of the row, so our users can actually see if their tasks are done.
Checkmarks
Now, open TodoRow.swift again. Make sure the Canvas is shown. Let’s make some changes as follows:
- We wrap the content of the row in HStack;
- We create another element in the stack, it’s an Image. We use system images here, checkmark.square.fill if the Todo is completed and square — if it’s not. You can find the names of all the system images in the SFSymbols tool that you can download on Apple’s site;
- We make the image .resizable(), otherwise, it’s too small for our rows, so we need to make it a bit bigger;
- We set the width and height for the image;
- We set a blue color for the image if the Todo is completed;
- We add a Spacer() in the end of the stack so all the content of the row will stick to the left side;
- To see the row in both states, when a Todo is completed and when it’s not, we wrap the TodoRow in the TodoRow_Previews into a Group;
- Below the current row, we add another one with a Todo that is completed;
- We move the .previewLayout modifier to the Group to apply that modifier to all the rows.
Awesome work! Now you see two rows in the Canvas and know how they look like. By the way, you can adjust them a bit, add some colors or other details if you feel like it.
Action
It’s been a while since we started this project. Now, it’s time to add the main action for any To-do list application — checking tasks as completed. We did great work, so it shouldn’t be hard now. Let’s get back to the TodoListView.
We’ll create an action when a user taps a row. To do so, wrap the TodoRow into a Button:
Now, run the Live Preview by tapping the top button in the right-bottom corner of the preview in the Canvas. When it’s loaded, try to tap the rows. Nothing’s happening. We probably forgot to update the Todos after changing the data.
Go to the TodoListViewModel. Find the toggleIsCompleted method. You see, when we ask the DataManager to make changes we don’t refetch the actual data. Add fetchTodos() in the end of the method:
Great. Now our Todos disappear when we tap them! But what if we want to see the completed Todos in our list?
Show/hide completed tasks
To appear or disappear our completed Todos we’ll add a new button in the navigationBar. Let’s do it:
- We added a new variable; it toggles the showCompleted variable in the viewModel. We use two system images, checkmark.circle.fill and checmark.circle depending on the current state of the showCompleted variable.
- We added the leading parameter into the .navigationBarItems modifier and put the button there.
Let’s try it out. Run the Live Preview and tap the button. What’s going on? The button’s changing, but nothing’s happening to the list of Todos. Well, I guess we missed something in our viewModel. Let’s go to our TodoListViewModel.
It’s a good approach to cover all the code with tests and MVVM provides us with a great opportunity to do that, but it’s not a topic of this tutorial, and you can see how bad it is to not have tests for our app.
We can see that when we change showCompleted the array of Todos doesn’t get changed. We’ll fix it easily by adding an action on didSet:
Now, get back to TodoListView and run the Live Preview. Try tapping on the showCompletedButton and our Todos. You see how everything gets changed, the completed Todos appear and disappear, checkmarks change, all that’s happening with animation. Way simpler than the implementation of UITableViewDataSource & UITableViewDelegate, isn’t it?
Now, you can run the app on your device or simulator, add some Todos, check and uncheck them and see that everything works. Great job!
There is a SwiftUI bug that the plus button works only once and doesn’t react on the following taps. I hope Apple will fix it soon, but there’s a little hack for now. After closing the modal view that appears on the plus button tap, scroll the list a bit so the navigationBar stretches a bit. After that, the state of the button reloads, and you can use it again. It’s not really convenient but we’ll leave it as it is for now and we’ll wait till Apple fixes it.
What’s Next
Great job. You’ve created a To-do list app that uses SwiftUI and Combine, you’ve implemented the MVVM pattern and used Protocols to make your code clean, readable and testable. The app still lacks a persistent data storage (and, actually, many other features) and we’ll add some storage in the next parts. While doing so, we’ll see how great it is to refactor an app when you use Protocols and how easily you can replace any part of an app and other parts notice nothing. Next part is available here.
The complete code of the app is available here.
This tutorial is the 3rd part of the series of the creation of a To-do list app. To check the other parts use the following links:
- Getting Started with SwiftUI and Combine Using MVVM and Protocols for iOS.
- Creating an iOS App With SwiftUI and Combine Using MVVM and Protocols.
- Creating an iOS App With SwiftUI, Combine, MVVM, and Protocols [Part 3]. (this tutorial)
- How to Create Your Own Framework for Your iOS App.
- Using Core Data in Your SwiftUI App with Combine, MVVM and Protocols.