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 4 of 4 of this article. Click here to view the first page.

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 …

Xcode console Realm migration crash

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?

Working With Migrations

When your app launches, Realm scans your code for classes that subclass Object. When it finds one, it creates a schema for mapping the model to the database.

When you change an object and add a property, there’s a mismatch between the new schema and the one in the database. If that happens, Realm throws an error. It doesn’t know what it should do with the old objects without the new property. You have to tell Realm how to migrate the old schema to the new one. Otherwise, it doesn’t know how to map old objects to the new schema.

Because you added a new property, colorOption, to Ingredient, you must create a migration for it.

Note: You can solve this during development by passing true to deleteRealmIfMigrationNeeded when you instantiate Realm.Configuration. That tells Realm that, if it needs to migrate, it should delete its file and create a new one. Another way to solve this, during development, is simply deleting the app and building and running it again. This deletes the realm on disk and creates a brand new one from scratch.

Creating a Migration

In the Models group, create a file named RealmMigrator.swift.

Now, add this code to your new file:

import RealmSwift

enum RealmMigrator {
  // 1
  static private func migrationBlock(
    migration: Migration,
    oldSchemaVersion: UInt64
  ) {
    // 2
    if oldSchemaVersion < 1 {
      // 3
      migration.enumerateObjects(
        ofType: Ingredient.className()
      ) { _, newObject in
        newObject?["colorOption"] = ColorOptions.green
      }
    }
  }
}

Here's the breakdown:

  1. You define a migration method. The method receives a migration object and oldSchemaVersion.
  2. You check the version of the file-persisted schema to decide which migration to run. Each schema has a version number, starting from zero. In this case, if the old schema is the first one (before you added a new property), run the migration.
  3. Finally, for each of the old and new Ingredient objects in Realm, you assign a default value to the new property, colorOption.

Realm uses migrationBlock to run the migration and update any necessary properties.

At the bottom of RealmMigrator, add the following new static property:

static var configuration: Realm.Configuration {
  Realm.Configuration(schemaVersion: 1, migrationBlock: migrationBlock)
}

You create a new instance of Realm.Configuration using your migrationBlock and set the current version of the schema to 1.

You'll use this configuration with your default realm now.

Open AppMain.swift and add the following line just below ContentView:

.environment(\.realmConfiguration, RealmMigrator.configuration)

Realm uses this configuration to open the default database. When that happens, Realm detects a mismatch between the file-persisted schema and the new schema. It then migrates the changes by running the migration function you just created.

Build and run again. This time the crash is gone!

Screenshot of PotionsMaster app working once again

You've added a new property to Ingredient. And you've taken care of the migration. Now it's time to update the row so users can see the color they choose!

Updating Ingredient Row

Open Ingredient.swift and add the following extension at the bottom of the file:

extension Ingredient {
  var color: Color {
    colorOption.color
  }
}

This adds a convenient computed property to get the color of the selected option.

Next, open IngredientRow.swift and find the following code:

Button(action: toggleBought) {
  Image(systemName: buttonImage)
    .resizable()
    .frame(width: 24, height: 24)
}

Add the following view modifier to the image, inside the button:

.foregroundColor(ingredient.color)

Here, you change the foreground color of the icon to toggle an ingredient as bought to the color the user selects for that ingredient.

Build and run to see the changes. Now, create a new ingredient and select a color for it.

Ingredients list with colored items

Great job! Now, you can list all the ingredients you need for that special potion you're brewing. :]

Where to Go From Here?

You can download the final project by clicking the Download Materials button at the top or bottom of the tutorial.

In this SwiftUI Realm tutorial, you learned to create, update, fetch and delete objects from Realm using SwiftUI. In addition to the basics, you also learned about migrations and how to create them.

To learn more about Realm, you can refer to its official documentation.

If you want to learn more about SwiftUI, see the book SwiftUI by Tutorials.

I hope you enjoyed this tutorial. If you have any questions or comments, please feel free to join the discussion below.