In this tutorial, we use the latest version of Xcode (12.4) and macOS Big Sur (11.2.1) for the moment of writing.
In this tutorial, we will use the app developed in the Complex SwiftUI App Tutorial. Part 1. Designing Model tutorial, so I recommend going through it. Otherwise, if you do not want to do this, you can simply download the starter project here.
What We Will Do Today
Today, we will create a view that will present a grid of your goals and another one to add new ones. We will make our code clean and testable and make sure the grid of goals gets updated, when a new goal is added. With time, we will add additional logic layers, views and learn how to make a (relatively) complex app with SwiftUI.
First, make sure you have your starter app ready. It should contain:
- Core Data model file,
- Two files containing Core Data entities
ContentViewshowing a simple list of goals stored in our temporary persistence for previews.
If you build and run this app, you will not see any data there, so we need to implement adding new goals. But before doing so, let’s first create a nicely looking view showing our goals using
As I mentioned in the previous part, we are going to use a controversial approach when the view will retrieve a list of goals directly from Core Data using
@FetchRequest, while the rest of logic will be in a viewModel. This is unusual, but we are trying to use some useful SwiftUI stuff, such as this property wrapper.
First, create a new SwiftUI file called
MyGoalsItemView. Make sure both targets (iOS and macOS) are selected. We will not check if it works in macOS at first and focus on the iOS implementation, but at some point we will add macOS support. Add the following to the new file:
- We add the
- We create a
VStackin the body and put two
Textelements there. First one will contain the goal’s
icon, add a modifier to set its font size to 60. The second
titlewith three modifiers: 1) we set the font’s color as
primarymeaning it will be black for the Light Mode and white for the Dark one, 2) we set
lineLimitto 2, 3) we set
minimumScaleFactorto 0.5, so if the text is too long, it will get up to 2 times smaller,
- We add modifiers to the
VStackto stretch horizontally and vertically as much as possible (to have an equal width and height for each item), set aspect ratio, so our items are rectangular, add padding, set the background color, corner radius and apply a shadow to make the view look nice,
- We add a static variable for a
- We pass the static variable into our view for the preview,
- We add a modifier to present our view in a fixed size layout, 160x160 pixels.
Now, let’s create a GridView we will use
MyGoalsItemView in. Go to
ContentView.swift and first of all, rename
MyGoalsView, as it makes more sense than
To rename your struct (class, method or whatever) everywhere, simply hold Command key and click on the struct name
ContentViewand then click Rename. Rename your struct to
MyGoalsView, then click OK. Now, you should see it is renamed. So is the filename of the files it is declared in.
Now, we will make the following changes in the file:
- We add a new variable
columnsthat contains an array with just one
.adaptivemeaning the grid will be filled with items of 100–160px width depending on the size of your device. On iPhone, it will have 3 items in a row while there might be more on iPad,
- We add a
LazyVGridinside that presents our goals, each in its
Now, if you check the preview in the Canvas, you will see our view with the grid of goals:
But if we build and run the app, we will see there is nothing there. We need to add a capability to add a new goal, so we can actually start tracking how we achieve our goals and go toward our dreams. But first of all, let’s implement a view, showing there are no goals instead of the grid.
For now, we will simply present a
Text and at some point we might want to create a separate view for this that will have an image and/or a button. But for now, add these changes:
- We added an
if-elseblock that presents a simple
Textwhen there are no goals,
- We put all our body in
Groupso we can apply
.navigationTitleto the whole code instead of attaching it to the new
- We also added a
NavigationView, so our view will have a navigation bar with a title,
- We added a title to the navigation bar of the view.
Great job! Now, it’s time to create a view where we will be able to add a new goal to our app.
First, create a viewModel for our
AddNewGoalView. Create a file
For now, it contains only
icon for a goal. Also, it has the
save method that we will implement later.
Also, it uses
GoalIcon does not exist yet. Let’s fix it. Create a new file
GoalIcon.swift. We will make it simple for now but might need to refactor later:
Great. Now, we have our viewModel and ready to create a view.
Create a new
SwiftUI View file, call it
AddNewGoalView.swift. This view will contain a
TextField to set the goal’s title and a selector to choose an icon.
viewModel to the view and add a
TextField to the
Note that we put the
TextField in a
Form. Now, we will add another
Section to the
Form with a
Picker for icons:
Last but not least, we will add a top bar to the view with two buttons,
- We add an
presentationMode, so we can dismiss this view,
- Embed the
- Add a
Cancelwill simply dismiss the view, while
Createwill call the
save()method in the
viewModeland then dismiss the view.
We will need a layer that will manage Core Data entities, because we do not want to expose Core Data in every screen working with our model. We will create
DataManager that, for now, will be able to create a new
- Create a protocol
- Create a
typealias DataManagerProtocol. For now, it will be just
GoalDataManagerProtocol, but later, we will add other protocols and
DataManagerProtocolwill be a general protocol including all related to the
- Create the class,
- Implement a
positionfor all the existing goals and create a new one in the 0th position.
Note that for now, we use
DataManager. It may make sense to create a new background context for our methods in the future, but for now, we are fine with this implementation.
Now, we will add the method to
- Inject our new
- Use the
Now, we need to add a button to the main view to present
AddNewGoalView, let’s do this. Open
MyGoalsView.swift and add the following:
- Add a new
- Add a
.sheetmodifier that will present
addNewButtonthat will set
- Add this button to the view using the
Now, build and run the app. Then try to create a new goal. Notice that, once created, the goal appears in the main view because our
@FetchRequest reacts n the change in the context.
Congratulations! We have created the main view in our app, and now we can add goals for us to stick to and obtain (or get rid of) new habits! We used SwiftUI and Combine, the MVVM pattern and used Protocols to make your code clean, readable and testable. In the next part we will add a
ContextMenu to the grid items, so we can edit and delete our goals. Also, we will add a view to edit our goals and finally, we will make our goals tappable, so we can mark them as completed for a day to start recording our progress on how we become better with time.
The complete code of the app is available here.
The next tutorial is available here.
This tutorial is the 2nd part of the Complex SwiftUI App Tutorial. To check the other parts, use the following links: