Realm With SwiftUI Tutorial: Getting Started
Learn how to use Realm with SwiftUI as a data persistence solution by building a potion shopping list app. By Renan Benatti Dias.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Realm With SwiftUI Tutorial: Getting Started
30 mins
- Getting Started
- Project Structure
- Working With Realm
- Understanding the Realm Database
- Setting up Realm
- Defining Your Realm Object Model
- Realm Property Types
- Relationships
- Fetching Realm Objects
- Understanding @ObservedResults
- Adding Objects to the Database
- Updating Objects
- Toggling Ingredients to BOUGHT
- Updating Other Properties
- Deleting Objects
- Adding a New Property to a Realm Object
- Storing the New Property in Realm
- Working With Migrations
- Creating a Migration
- Updating Ingredient Row
- Where to Go From Here?
Updating Objects
A key feature of PotionsMaster is the ability to toggle an ingredient to the BOUGHT list. Right now, if you tap the buy button, nothing happens. To fix this, you use Realm to update ingredients on disk.
Toggling Ingredients to BOUGHT
To move an ingredient to the BOUGHT list, you need to update the property value of bought
to true
on disk.
Open IngredientRow.swift and import RealmSwift
.
import RealmSwift
Next, replace the following line:
let ingredient: Ingredient
With the following:
@ObservedRealmObject var ingredient: Ingredient
Once again, you’re using ObservedRealmObject
to observe changes to an ingredient in the database and update the view when its properties change.
Next, find and replace the contents of toggleBought()
with the following:
$ingredient.bought.wrappedValue.toggle()
This code toggles the bought property of that ingredient to true if the ingredient hasn’t been bought yet, or to false if the ingredient has already been bought.
Notice the $
before the ingredient. Instead of calling write(withoutNotifying:_ :)
to start a write transaction, you use $
in your Realm object to automatically start a write transaction and update the value of bought
.
Build and run. Tap the circular icon on the right of the row to buy an ingredient.
Awesome! You update ingredient
, and Realm notifies the results inside IngredientListView
to update the list. How cool is that? :]
You can also update objects by starting a write transaction and updating each property of the object inside that transaction.
You’ll do this next to update the rest of the properties of an ingredient.
Updating Other Properties
Now that you know how to update an object, you can use Realm to update other properties just as easily. Go back to IngredientFormView.swift and replace the computed property isUpdating
with the following code:
var isUpdating: Bool {
ingredient.realm != nil
}
This updates isUpdating
to check if the ingredient in the form has been persisted in the database. When you tap New Ingredient, you pass to IngredientFormView
a new instance of Ingredient. This is an unmanaged object. That means the database doesn’t know about it yet and any changes won’t persist until you add it to Realm.
When you open IngredientFormView
with a new instance of Ingredient, this instance doesn’t have a realm yet, meaning it has yet to be saved in Realm. However, when you open IngredientFormView
by tapping a row and passing its ingredient to the form, this ingredient has already been stored in the database and, thus, you’re updating it.
Build and run. Tap an ingredient in the list to open the form.
Update its properties and tap Done.
Fantastic! You didn’t have to change a single line of code in the body
of the view to update the ingredient. Realm objects have bindings to SwiftUI controls and, much like toggling the bought property, they already start a realm transaction to write the changes in the database whenever you change those values.
Now, all that’s left is deleting ingredients!
Deleting Objects
Tapping the buy button moves an ingredient to the BOUGHT section. But once it’s there, you can’t get rid of it. You can tap the blue icon again to move it back, but you can’t remove it from the list.
You’ll use the List
‘s swipe gesture to remove ingredients from the list.
Open IngredientListView.swift and find the following code in the second Section
:
ForEach(boughtIngredients) { ingredient in
IngredientRow(ingredient: ingredient)
}
Next, add the following just under the ForEach
view:
.onDelete(perform: $boughtIngredients.remove)
onDelete(perform:)
is a view modifier for adding a delete action to the row of a list. It passes an IndexSet
of the items to remove from the list.
You use Realm’s Results
type to remove(atOffsets:)
binding for removing those objects from Realm. This creates a write transaction and removes the swiped ingredient from Realm. Realm takes care of updating boughtIngredients
, and SwiftUI updates the list.
Build and run the app. Buy ingredients and swipe left to delete them from the database.
Adding a New Property to a Realm Object
During development, it’s common for data models to grow and evolve. Property types might change, and you might need to add or remove properties. With Realm, changing your objects is as easy as changing any other Swift class.
In this section, you’ll add a new property to identify your ingredients by color.
Inside the Models group, create a new swift file and name it ColorOptions.swift. Add the following code to the new file:
// 1
import SwiftUI
import RealmSwift
// 2
enum ColorOptions: String, CaseIterable, PersistableEnum {
// 3
case green
case lightBlue
case lightRed
// 4
var color: Color {
Color(rawValue)
}
// 5
var title: String {
switch self {
case .green:
return "Green"
case .lightBlue:
return "Light Blue"
case .lightRed:
return "Light Red"
}
}
}
Here’s a breakdown of this enum:
- First, you import
SwiftUI
andRealmSwift
- Then, you define an enum named
ColorOptions
and extend it to String. You also conform it toCaseIterable
, to list all cases of the enum and toPersistableEnum
. This is a protocol Realm uses to store enum values in the database. - Next, you define three cases,
green
,lightBlue
andlightRed
. Those are the color options users will choose for each ingredient. - Here, you add a computed property to instantiate a
Color
from the project assets. - Finally, you define another computed property for the title of each case.
Now, open Ingredient.swift and add a new property under bought
:
@Persisted var colorOption = ColorOptions.green
You add this property to store a color option with the ingredient in the database.
That’s it! The models are ready to store a color. Next, you’ll update IngredientFormView.swift to save this new property in your database.
Storing the New Property in Realm
Now that you’re ready to store the new color, it’s time to update IngredientFormView.swift to let users select the ingredient color. You’ll use a Picker
view to allow users to select the color from the available options.
Open IngredientFormView.swift and add the following property:
let colorOptions = ColorOptions.allCases
This adds a property to store all cases of ColorOption
. You’ll use this to list all color options in the picker view.
Next, find the comment // To Do Add Color Picker
and replace it with the code below:
Picker("Color", selection: $ingredient.colorOption) {
ForEach(colorOptions, id: \.self) { option in
Text(option.title)
}
}
This adds a Picker
view to the form, binding the property colorOption
form to the ingredient, with all the cases from ColorOption
.
Build and run. And you get …
The app crashes as soon as you open it! Realm throws a migration error: Migration is required due to the following errors
. But why is this happening?