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.

5 (5) · 3 Reviews

Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Defining Your Realm Object Model

Open and look at Ingredient.swift, inside the Models group.

This class defines an ingredient in the domain of PotionsMaster. Notice it’s a plain Swift class with Published properties and an id to identify each object.

This object is used inside the list of ingredients and inside the form to create or edit an ingredient. However, if you try to modify any ingredient or create a new one, you see the list doesn’t change. Even though you can bind the properties of this class to the form, you’re not yet persisting any changes.

You’ll modify this object to store it in Realm now.

Still in Ingredient.swift, begin by adding the import for RealmSwift:

import RealmSwift

and then replace the declaration of the class with the following:

class Ingredient: Object, ObjectKeyIdentifiable {

Object is a type alias to RealmSwiftObject. This is the class Realm uses to store data inside a realm. By subclassing Ingredient to it, you’re able to store this class in the database.

Like Identifiable, ObjectKeyIdentifiable is a protocol Realm uses to identify Realm objects.

Next, replace the following lines:

@Published var title = ""
@Published var notes = ""
@Published var quantity = 1
@Published var bought = false

With the following code:

@Persisted var title = ""
@Persisted var notes = ""
@Persisted var quantity = 1
@Persisted var bought = false

@Persisted is a property wrapper like Core Data’s @NSManaged. It defines properties as managed by the Realm framework, allowing it to store their value.

Realm Property Types

Although you’re still using regular Swift classes to store objects in it, Realm has a limited set of platform-independent property types because of its cross-platform nature.

You can define properties with the following types:

  • Bool
  • Int, Int8, Int16, Int32, Int64
  • Double
  • Float
  • String
  • Date
  • Data
  • Decimal128

Every property of Ingredient is declared with a default required value. But you can declare optional properties, too. To define an optional property, all you have to do is make the property optional, like any other Swift property.

Relationships

Realm also supports relationships. You can declare nested objects to create many-to-one relationships. And you can use List to create many-to-many relationships. When declaring a List, Realm saves those nested objects together with your model.

Before you move on, find this code inside Ingredient.swift:

let id = UUID()

And change it to the following:

@Persisted(primaryKey: true) var id: ObjectId

This changes the ID from UUID to ObjectId and uses the Persisted(primaryKey:) to set this value as the primary key of this object. The framework uses this property to enforce uniqueness of objects and to fetch objects from Realm.

Now that you’ve defined your Realm object, it’s time to write code to fetch and add objects to your database.

Fetching Realm Objects

Fetching objects from Realm is simple. You can instantiate a realm and fetch objects from it. However, the Realm team created handy property wrappers, which are like Core Data’s FetchRequest, to use Realm with SwiftUI.

You’ll use them to fetch objects from Realm and observe changes to the database to update the view whenever you add, edit or delete an object from Realm.

Inside Views, open IngredientListView.swift. Add the RealmSwift import:

import RealmSwift

and find the following two State properties:

@State var ingredients: [Ingredient] = []
@State var boughtIngredients: [Ingredient] = []

Replace them with the following:

// 1
@ObservedResults(
  // 2
  Ingredient.self,
  // 3
  where: { $0.bought == false }
) var ingredients

// 4
@ObservedResults(
  Ingredient.self,
  where: { $0.bought == true }
) var boughtIngredients

Here’s a breakdown of the code:

  1. You define a property, ingredients, and mark it with @ObservedResults.
  2. The first parameter of @ObservedResults is the type of the object you want to fetch from Realm: in this case, Ingredient.
  3. You also use a where closure argument to filter for only the ingredients that haven’t been bought.
  4. Here, you define another property, boughtIngredients, and also mark it with @ObservedResults. However, this time, you filter the ingredients that have been bought.

However, it’s often better to use the where form of object filtering, because that tells the Swift compiler to check that the bought property of is valid syntax. If you write a predicate string incorrectly or update the property names of the data class without also updating the predicate string, it might result in a runtime error because Swift can’t check inside the string for valid syntax!

Note: Support for the where closure argument was recently added. It’s still OK to use the previous filter argument calling style that takes an NSPredicate to filter your objects like Core Data does. For example:
@ObservedResults(
  Ingredient.self,
  filter: NSPredicate(format: "bought == true")
) var boughtIngredients
@ObservedResults(
  Ingredient.self,
  filter: NSPredicate(format: "bought == true")
) var boughtIngredients

Understanding @ObservedResults

@ObservedResults is a property wrapper you can use to fetch and observe objects from a realm. This property fetches objects and returns a Results type, which is a type from Realm that represents a collection of objects retrieved from queries.

By replacing both arrays in your view and adding those two properties, you’re telling Realm to fetch ingredients from the database and display them in the view. And you can even filter the results, just like you’re doing with the bought ingredients.

This is a powerful property wrapper. Whenever you add or remove objects that are ingredients in the database, Realm updates those properties and SwiftUI updates its views.

Before you move on, open ContentView.swift and replace the code inside NavigationView with the following:

IngredientListView()

This instantiates an IngredientListView without passing any values to the properties.

Build and run the project.

Empty list of ingredients

You’re not seeing the mocked data anymore and the list is gone. That’s because the database is empty and there’s nothing to list. It’s time to populate the database with objects.

Adding Objects to the Database

Open IngredientFormView.swift and import RealmSwift at the top of the file.

import RealmSwift

Next, find the following property:

@ObservedObject var ingredient: Ingredient

And replace it with the following:

@ObservedRealmObject var ingredient: Ingredient

ObservedRealmObject is a Realm property wrapper that behaves much like ObservedObject. You use it to observe changes to the state of Realm objects and update the view whenever the object changes. You’ll also use it to update the object in the database later.

Next, at the top of the view, add the following environment property:

@Environment(\.realm) var realm

This environment property stores the default realm you use to store and fetch objects from the database.

Finally, find the method save() at the bottom of the file and replace the contents of it with the following:

try? realm.write {
  realm.add(ingredient)
}
dismiss()

Here, you start a write transaction by calling write(withoutNotifying:_:) on the Realm object. Each operation you make on a realm must be inside this write transaction block, including additions, deletions and updates. Inside the transaction, you add the new instance of Ingredient to Realm. Realm now stores the object and tracks its changes, making it a managed object.

Build and run. Tap New Ingredient and type a title and note. Save the ingredient to see it in your list.

Ingredients list showing ginger

Success! You can now add and list objects in the app. When you open IngredientFormView, you also pass an empty ingredient object to it. When you add this ingredient in the database, Realm updates the property ingredients inside IngredientListView and SwiftUI updates your view. Feels like magic, right? Go ahead and create some more ingredients! :]

Now that you can successfully add an ingredient, it’s time to build the functionality to update existing ingredients.