Build your first SwiftUI app (Part 2): Project architecture

Build your first SwiftUI app (Part 2): Project architecture

In the first article of this series, we set up a new XCode project, installed CocoaPods, configured SwiftLint, and enabled localization support in the app. We will continue exactly where we left off, so if you missed the first article, read it first!

Now is the time to start thinking about project architecture. There are many iOS architectural patterns that we can use:

  • MVC (Model — View — Controller),
  • MVP (Model — View — Presenter),
  • MVVM (Model — View — View Model),
  • VIPER (View — Interactor — Presenter — Entity — Routing).

We'll use the MVVM architectural pattern for our SwiftUI app because it's practically built into SwiftUI and is easy to use for both development and testing. Apple also recommends it for SwiftUI. We don’t have enough space to compare and discuss all of these architectures in this article, so I advise you to do some research on different SwiftUI architectures to see what the pros and cons are of each one. Here are some very interesting articles on the subject that you could start with:

SwiftUI App Architecture
Advanced SwiftUI Architecture for easy Testability and Data Mocking
SwiftUI: Choosing an Application Architecture
It’s hard to decide if you don’t know what you need.

While working in any architectural pattern, keep in mind that it is practically impossible to follow it 100%, and that you will adapt your project architecture and code as you see fit, depending on your requirements. Simply look at this version as a starting point, which will grow and change organically along with your app.

With that said, let’s start!

Folder and file structure

Every project architecture starts with a decision on how to organize folders and files. There are two most commonly used approaches: function-based and feature-based. We will be using the feature-based approach in our app because it is much more clear and easy to maintain in the long run. If you want to learn more about the differences between these two approaches, this article is very good reading material:

Simplifying Projects with a Folder-by-Feature Structure
Learn how to simplify code and organize complex development projects using John Papa’s Folder-by-Feature structure.

Since it’s a feature-based approach, the first step is to define what our features will be. Let’s assume our app will have the following features:

  • Login
  • Home
  • Settings

That means we will have three folders: LoginHome, and Settings inside our Features folder, which will reside in our Code folder, so it will look like this:

Since we are using MVVM, we need special folders in each feature to hold all of our future view, model, and view model files. Therefore, each one of our feature folders will contain subfolders: ViewsModelsand ViewModels.

However, from my experience, it would be very good for readability to actually make a distinction between views that represent our app screens and views that are just small atomic components, such as buttons or labels (we will use them in our screens). So, let’s create another folder called Screens, where we will put these container views.

Also, it is a good practice to have a designated place (folder) in each feature that contains code that handles API requests that the feature will use. This is important for two reasons. The first reason is that we don’t want our view models to be too big by filling them with API handling code. The second, even more important reason is that we want to keep our business logic (which belongs in a view model) separate from our API logic, which should be very simple and only care about making the API request and decoding the response.

Let’s place our API requests in a special folder called Actions. All that our actions will do is create API requests, decode responses, and pass the results to the view model. Nothing more. All custom logic that happens after receiving the response will be handled in the view model. This way, we can always call actions from our view models whenever we need to make an API request, treating them as black boxes.

You will definitely have files that don't belong to a single feature but can be used in different parts of your app. One example of this would be a button that you use on many different screens of your app. Another example is an API request that you are making on a global level, such as fetching logged user data. This means we also need a Common feature folder to hold all files that affect more than a single feature.

Finally, let’s sort all these subfolders by name, so they're consistent. With all of these changes in place, our folder structure will now look like this:

Alright! Now we can easily locate our features and have a clear picture of where everything is in our app. This approach also makes it very easy to add or remove features without affecting a bunch of files across folders all over your project. While this folder structure might seem overwhelming at first, it is definitely the way to go if you are building an app that is expected to grow in size, because you can always locate and work with your files easily, regardless of how many features you have. Even if your app has 20 features, you can simply focus on the feature folder you are currently working on, and ignore (most of) the other folders in your project.

Since the Settings feature was mentioned just for demonstration and we won’t really make a Settings feature in this series, let’s remove the Settings folder and erase this entire functionality from this project. Yes, it’s that easy!

Keep in mind that this is only a skeleton of what you might need and it is expected to grow and adapt over time as you keep adding features to your app. For example, you might need to add a ViewModifiers folder inside your Common folder where you will put all of your view modifier files.

Of course, you might not even need all of these subfolders for each feature. For example, if your feature doesn’t have any actions, then there is no purpose to having the Action folder in it.

Next, let’s think about what else we will need in our app so we can create corresponding folders. Since we plan to cover our app with tests, we definitely need a Tests folder. We will certainly have some kind of resource files within the project, such as animations, fonts, JSON files, images, or even video files. These can go in Resources folder. Another very good idea is to create a Config folder where we will put all of our configuration files, such as app environments, app configurations, launch arguments, and all custom configuration files we will use.

Our top-level folder structure (feature folders are collapsed in this screenshot to save space) will now look like this:

Tidy up

Before we dive into making features, now is a great time to tidy up our files a little. Let’s start by moving our .swiftlint configuration file to the Config folder, where it belongs. However, doing this will make our configuration file stop working because the location has changed! To fix this, we need to update the SwiftLint build script that we added earlier with the correct file location:

"${PODS_ROOT}/SwiftLint/swiftlint" --config 
"$SRCROOT/SwiftUIBlueprint/Config/.swiftlint.yml"

Now our build script will look like this:

Next, we need to put our ContentView and SwiftUIBlueprintApp somewhere, so they don’t hang in the root folder. This is just a personal preference of mine. I just don’t like having tons of files in the root folder, as the project navigator can become ugly quickly. We could move these files somewhere inside our Code folder because, technically, they are nothing more than code. But these two files are very unique and special to the project. SwiftUIBlueprintApp, for example, is our App file whose purpose is to launch ContentView, which is our root view that starts the entire app. So, let’s keep these files in the Supporting Files folder, where we can always find them easily without having to search too much.

Finally, we have our asset catalog file called Assets.xcassets. It can be used for many different purposes, such as holding the app icon, any images that we need in the app, or even holding our app colors. But as our app grows, more and more assets will be added to this file, and it will be impossible to keep track of hundreds of different assets in a single asset catalog file.

It is much better to hold each one of these different asset types in a separate file—one asset catalog for images, another one for the app icon, maybe a third one for country flag icons (if we ever need them), and so on. For now, let’s just rename this file to AppIcon, and we will use it only for storing the app icon. When we need images or any other asset catalog, we will create a new .xcassets file for that purpose. Because assets are resources, let’s create a folder called Images in our Resources folder and put our AppIcon file there.

After all this, this is our updated folder and file structure:

And this is how it looks collapsed:

Pretty neat, huh? 😉

We now have everything we need, so we are officially ready to actually write some code and populate some of these folders!

Demo project ❤️️

If you want to download a project that contains everything we’ve done so far, you can do it by clicking here.

Build your first SwiftUI app” is part of a series of articles designed to help you get started with iOS app Development.

If you have any questions on any of the above-outlined thoughts, feel free to share them in the comment section.

Click here to read Part 1.: Project setup

Next part ⏭️

We have prepared our project structure, and now we are ready for coding.
In the next article, we will start crafting our login feature, so stay tuned!