iOS App Development
Getting Started with SwiftUI and Combine Using MVVM and Protocols for iOS
In this tutorial we will create a simple To-do list app and learn how to get started using 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.2) for the moment of writing.
Apple introduced SwiftUI and Combine on WWDC 2019. SwiftUI provides a declarative way of creating interfaces that simplifies and speeds up the development of iOS apps, while Combine brings Functional Reactive programming (FRP) into iOS development that helps to focus on the business logic and easier separate the logic and appearance in iOS apps using MVVM.
Also, we are going to use Protocol-Oriented Programming (POP), since Swift pushes us to use more value types such as structs and enums that do not support inheritance but can take full advantage of POP. Also, Protocols allow structuring code and architecture apps without writing many rows of implementation. And, finally, they help to avoid tight coupling of classes with each other and makes it easier to write unit tests.
By the end of this tutorial, you will learn the basics of:
- How to use Protocols and write clean and testable code (we will not cover tests in this tutorial);
- How to use the Model-View-ViewModel paradigm to separate your UI with logic;
- How to work with SwiftUI alongside with Combine to create UI and bind it with your logic.
So, let’s start by creating a new project. Open Xcode, choose Create a new Xcode project (or File — New — Project…). Then choose Single View App under the Application section in the iOS tab and click Next.
Type Product Name. You can use DemoToDoList or whatever you want. Choose Team, type Organization Name and Organization Identifier.
Select Swift in Language and SwiftUI in User Interface.
Your options should look like this:
Click Next, select the path where to store your project and press Choose.
After that, you will see your workspace. In the left panel, you will see Project Navigation with all the files you have so far. Choose ContentView.swift. On the right side, you should see Canvas. You can easily turn it off and on pressing Cmd+Option+Return. If your View is not shown on Canvas, press the Resume button at the top of Canvas.
You should now see something like this:
Great! Now you have your “Hello World” app. If you build and run the app, it will show the same view in the simulator.
Now change the text inside Text(…) to something else, i.e. “Hello, SwiftUI!”. You can see that the text updates on the canvas.
Now, click your text in the canvas holding the Cmd button. In the pop-up menu click Show SwiftUI inspector. Now, you can see the inspector with parameters that you can change. Select Title in the Font selector. The text should become bigger and you can see that there is code added under Text(“Hello, SwiftUI!”).
Now, add .foregroundColor(.green) below .font(.title) in the code. You will see that the text is now green.
As you can see, you can change your SwiftUI Views in both code and canvas. It is pretty useful and allows you to work with views easier. Also, you will no longer have problems with merging your storyboards if you work in a team. You do not need storyboards anymore.
Let’s start writing code with the least entertaining part of the app. We need a model for our tasks that we are going to add to our to-do list. Create a new file (File — New — File or simply Cmd+N), choose Swift File. Name it Todo and click Create.
We will keep it simple for now:
Great! We have a model. We use struct instead of class because Apple pushes us to do so, so I am trying to use structs wherever possible and change them to classes when it is more convenient or inevitable.
We need Todo to conform to Identifiable for the future use in our SwiftUI View.
Now, we need to have a source of data we can fetch the list of todos from (and add new and check them as completed).
Create a new Swift file as we did it earlier, name it DataManager.
First of all, we will create a protocol DataManagerProtocol and implement all the necessary for now actions: fetch all, create new, mark as completed/incompleted:
But what if we want to set a default value for the fetchTodoList method? Let’s use the magic of protocols and create an extension to the protocol (add it below the protocol in DataManager.swift:
Great! Now we can implement this method with default value as false to conform to this protocol. Isn’t that sweet?
(Also, you can see that in Swift 5 we no longer need to add the return word inside the method if it consists of just one row).
The next step is to implement all the methods in the DataManager class. For now, we will store everything in the array inside of the class (to make the tutorial simpler). Also, we will implement Singleton here and store the instance of the class in the shared variable, since we know that we need only one instance of that class:
We created shared to store an instance there, made init() private to prevent us from creating other instances of DataManager. We store the list of our tasks in the todos variable.
All the methods we implemented are simple for now:
- fetchTodoList returns an array of either all the todos or those that are not complete yet;
- add inserts a new Todo in the array;
- toggleIsCompleted marks/unmarks a Todo as completed.
I like to divide code using extensions for all the protocols, it helps to structure your code and avoid having a mess while scaling your project, so I strongly recommend that.
The next step is to create a protocol for our viewModel for the list of todos. Create a new Swift file a name it TodoListViewModel. Create a protocol as follows:
- We import the Combine framework since we will need to use it later while implementing the viewModel;
- We need to have an array of Todos to bind it with our view (we allow only to get it, the view should not be able to set it, we just do not need it);
- We added showCompleted with access to both get and set, so we can toggle it and show or hide completed todos;
- fetchTodos() will be called when we need to fetch our todos (obviously);
- Finally, we need toggleIsCompleted to be able to mark todos as completed.
Now, we are ready to implement that protocol in our viewModel. To do so we add a class below named TodoListViewModel. We need it to conform to the protocol we created, so let’s implement all the methods in the viewModel:
- We add conformance to the ObservableObject so we can observe the changes in the viewModel;
- We have todos and showCompleted with the @Published modifier because we need our view to be updated on any change of these variables;
- Our viewModel needs to use DataManager so we create a variable for that. The type of the variable is DataManagerProtocol because viewModel should not know anything about DataManager. It is very helpful when we test the viewModel, we just can create some MockDataManager that will conform to the protocol and initialize and test the viewModel without touching our real Data Manager (even though the Data Manager we created does not seem to be real, but let’s just pretend it is real for now);
- We create an extension for the viewModel that conforms to the protocol we made and implement all the methods there (except for todos and showCompleted, because we cannot put variables into an extension).
The methods in the viewModel just call the similar methods in the Data Manager, so it may seem to be unnecessary to create a Data Manager. If you think so, it is okay, just remember that we need to separate our code and divide it into different pieces to make it readable, scalable, understandable, testable (and many other -able). The DataManager is responsible for all the work with the data storage, while TodoListViewModel is responsible only for an appropriate representation of the model in the TodoListView that we will create in the next step.
Finally, it is time to create a view that will show us all the todos we have. Go to File — New — File… (or Cmd+N), choose SwiftUI View in the User Interface section of the iOS tab, name it TodoListView and click Create.
Make sure you have the canvas on the right side of Xcode. Otherwise, open it by clicking on the Adjust Editor Options button (in the top right corner) and choose Canvas in the context menu. Or you can show/hide it using the hotkey Cmd+Shift+Return. If Canvas does not show the view, click Resume in the top right button.
Create a view with the following code:
- We have the viewModel variable with the @ObservedObject modifier, so the view updates every time the @Published variables in the viewModel get updated;
- We create NavigationView to appear a navigation bar (we will probably add some buttons on it in the future and, for now, the view looks better with a title;
- We create List to have a table in our view, pass the todos array in it and use a simple Text as rows (we will fix it later);
- We add a title to the view;
- Finally, we call the fetchTodos() method of the viewModel when the view appears.
If you look at Canvas, you will not see any content and that is not good since we want to see how our view looks like. We need to pass some data inside TodoListView in the preview and that is where our protocols help again. Now, let’s create a new Swift file named MockDataManager (we can put it into a separate group in the project’s file structure named Mock, for example, and will be storing all the data for previews there).
We will just copy the code from the DataManager class and change it a bit:
The only difference is that we added todos in init() (also, we removed the shared variable, but it is not that important). I know it may look stupid that the code almost the same, but we will definitely change the code in DataManager, if we improve the project and make it real while leaving MockDataManager unchanged or almost unchanged. Please, just accept it as it is for now and let’s go to the next step.
Now, we will modify a bit TodoListViewPreviews in the bottom of the TodoListView.swift file:
Awesome, now we see a list of todos in Canvas!
One last thing
We need to change SceneDelegate.swift to open TodoListView instead of ContentView (the view initially created in any new SwiftUI project:
But if we build and run the app, we will see no todos in the list, because we have not added them in our DataManager. That is okay, it should be this way until we implement adding new todos in the app. The app is still incomplete and we still have to add many other features, but the tutorial is already too long, so we will do that in the next part.
Congratulations! You have learned how to use SwiftUI and Combine with MVVM and Protocols. In the next part we will add checkmarks to the rows, implement adding new todos and marking them as done and some other features that will make our app better and help to gain new knowledge.
The complete code of the app is available here.
Stay tuned, I will be back soon! Please leave your questions, suggestions, comments below. Thanks.
This tutorial is the 1st part of the series of the creation of a To-do list app. To check the other parts use the following links: