iOS App Development
Complex SwiftUI App Tutorial. Part 1. Designing Model
In this series of tutorials, we will create a complex app using SwiftUI, Combine, Core Data, MVVM pattern, and Protocols.

In this tutorial, we use the latest version of Xcode (12.4) and macOS Big Sur (11.2) for the moment of writing.
There is no shortage of SwiftUI tutorials nowadays, so everyone can find something that works most for them. I created such a series of tutorials too.
But those tutorials are mostly for beginners and do not cover much of the additional features that SwiftUI or Combine have. Nor do they have complex architecture or logic.
In this series, we will create a complex app with several views, several Core Data entities, and a lot of under-the-hood things. It will take a while, and of course, it won’t be a complete ready-to-publish app, but it’ll be something a bit more advanced.
So, in an attempt to make this really long series of tutorials as short as I possibly can, I will expect you have some basic understanding of SwiftUI, Combine, and the MVVM architectural pattern, and I won’t focus on simple things. However, if you find something difficult, feel free to let me know, so I can help you go through the series.
So, let’s begin.
What We Will Do Today
To make our journey a bit more enjoyable at the beginning, we won’t dive into architecting our business logic deeply, but we’ll create the main model for our app in Core Data and present it in a simple way in a SwiftUI view to make sure it works. As a result, by the end of this first part, we will have a foundation for our app and will focus on creating the main view and implementing basic actions so we can understand what exactly we’re going to make by the end of the series. After that, we will focus on adding logic, diving into the implementation of more complex parts of the app, and deciding what features our app will have.
Maybe it’s not the best approach, and in a real project you should firstly create most of the logic, cover it with tests and then make UI, but it’s not always the case, especially when it comes to tutorials. Anyway, we’ll try to make our process of creating this app as close to real work as possible while implementing both logic and UI simultaneously.
Idea
We all have tried to obtain a new habit or quit a bad one. In many times dedication and consistency work, and many of us may notice that after some magic number of days (21, 30, whatever) it gets easier to stick to your new habit or withstand a desire to get back to the old one you want to get rid of.
The app we are about to start making will be about habits. We’ll make an app to track whether you stick to your goal you’ve decided to stick to or quit a bad habit. We’ll record our progress each day and present some kind of statistics.
I’ll call it Daily Goals but you can choose any name for it. So let’s start.
Taking Off 🛫
Open Xcode, select Create a new Xcode project, select the Multiplatform tab and click on App and then click Next. Type your app’s name (Daily Goals or any name you like), choose Team and Organization Identifier, and make sure you checked Use Core Data (as well as Host in CloudKit), as we’ll use Core Data in our app. You can uncheck Include Tests, as we won’t cover them in this tutorial, although I believe it’s important to cover as much code as you can with tests in a real app.
Press Next, choose where you’d like to store your project’s files, and click Create.
Now, you should see a project with one simple SwiftUI view, PersistenceController, and folders (aka Groups) Shared, iOS, macOS.
Model
The first step is to create a Core Data model for our goal’s entity and get rid of Item
created by default. Select your xcdatamodeld
file.
Click on Add Entity at the bottom of the screen and you should see a new entity called Entity
. Rename it to TLGoal
. TL
stands for Tutorial but you can choose your own prefix.
I recommend using a prefix for your entities, so your naming doesn’t interfere with the naming of the Swift (or SwiftUI) classes, structs, property wrappers, etc. For instance, if we’re going to put our goals into lists, it won’t be a good approach to call an entity
List
because SwiftUI already has the viewList
. As a result, you’ll see weird compiler errors, won’t be able to easily use your entities and make your life a bit more difficult. While a prefix ensures, you’ll unlikely have such an issue.
Every goal will have an id, icon (we’ll use emoji for this tutorial), title, sorting index, date it’s created on, the date it’s updated last time on, flag showing if it’s deleted (we’ll keep our goals deleted for several days before actually removing them from the database, so that flag will come in handy).
Let’s add all the attributes to your new entity:
id
, typeUUID
,icon
, typeString
, non-optional, default value: empty string (you can set these settings in the Data Model inspector in the right panel,title
, typeString
, non-optional, default value: empty string,position
, typeInteger 16
, non-optional, default value: 0,addedOn
, typeDate
, non-optional, default value: any date in the past (you may leave the one added by default),modifiedOn
, typeDate
, non-optional, default value: the same as above,isRemoved
,Boolean
, non-optional, default value:NO
Also, let’s create an entity for storing records for our goals, so we may collect statistics on how the user performs over time. We’ll call it TLGoalRecord
, it’ll have just one attribute:
date
, typeDate
, non-optional, default value: any date in the past.
Also, add a relationship to this entity. To do this, click on the plus button in the Relationships group, name it goal
, destination will be TLGoal
. In the Data Model inspector, make sure it’s Delete Rule is Nullify
, Type is To one
.
Now, select TLGoal
and add a relationship to it. We’ll call it records
, destination will be TLGoalRecord
, Inverse will be goal
. Set Delete Rule to Cascade
, Type to To Many
.
Great! Also, let’s delete the automatically created Item
entity. Select it and press Delete on your keyboard.
As a final step for our Core Data model, let’s manually create Swift classes for our entities. First, select TLGoal
, then change Codegen to Manual/None
in the Data Model inspector. And then, do the same for TLGoalRecord
.
Boring database-related stuff is almost done. But there are a few extra steps required. First, select Persistence.swift, and comment or delete all the code related to the old entity Item
in the preview
variable, so it only returns an instance of PersistenceController
that is stored in memory. Do not delete this variable, we’ll need it later, as it’s convenient to create a Core Data model stored in memory for our SwiftUI previews (and for unit tests as well). We’ll add a creation of Goals in here, but later.
Now, open ContentView.swift and comment or delete everything related to Item
there. Basically, you need to add to the ContentView
class body
variable Text("Hello, world!")
and remove everything the compiler shows errors for. Make sure the app builds with no errors, so we won’t be interrupted while working on our app.
Model Files
First, create a new file TLGoal.swift: