Realm with SwiftUI Tutorial: Getting Started | raywenderlich.com

Learn how to use Realm with SwiftUI as a data persistence solution by building a potion shopping list app.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/32960966-realm-with-swiftui-tutorial-getting-started

Hi Renan,
What is the benefit of using an enum instead of a singleton for example for the RealmMigrator ?

Hi @chlorofom,
Great question! You want to use a Singleton for objects that you want to make sure they’ll only have a single instance in your code.

Since I’m setting the Realm.Configuration in the environment, and I’m not changing the configuration during runtime, I made a computed property for this object, making sure I can only get it to set the environment. Plus, this is a struct, so it’s pretty fast to instantiate should you need to use it again somewhere else.

A singleton would allocate memory for itself and be available during the duration of the app, but we’re only using this property to run the migration during the app launch, so there’s no actual need to keep an instance of the configuration.

I’m also using an enum because I want to make sure that there’s no way to instantiate a RealmMigrator. You’d only be able to access its static properties. If I did the same with a struct, you’d be able to instantiate RealmMigrator (and that makes no sense here).

Hope this helps you! :]

2 Likes

Thanks a lot for your answer.

I have an app using Realm in UIKit and I’m now trying to convert it to swiftUI.

I’m facing a crash when trying to delete an object from my Realm. The object is properly deleted, and my var containing my results is properly updated. But something is trying to access the deleted object after that causing the crash and I can’t find what.

Here is my code, I’d be really grateful if you could help me out.

Here is my List screen (I removed irrelevant code such as other UI) :

import SwiftUI
import RealmSwift

struct PrepFileListView: View {

    @ObservedResults(
        PrepFile.self,
        where: { $0.isDraft == false }
    ) var prepFiles

    @ObservedResults(
        PrepFile.self,
        where: { $0.isDraft == true }
    ) var drafts

    var body: some View {
        VStack {
            list
        }
    }

    [...]

    private var list: some View {
        SwiftUI.List {
            if !prepFiles.isEmpty {
                fileSection
            }
            if !drafts.isEmpty {
                draftSection
            }
        }
        .listStyle(.insetGrouped)
    }

    private var fileSection: some View {
        Section() {
            ForEach(prepFiles) { file in
                FileRow(file: file, viewModel: FileRowViewModel())
            }
        }
    }

    private var draftSection: some View {
        Section() {
            ForEach(drafts) { file in
                FileRow(file: file, viewModel: FileRowViewModel())
            }
        }
    }
}

Then here is my row view where I would like to be able to delete my object :

struct FileRow: View {

    @ObservedRealmObject var file: PrepFile

    var body: some View {
        Text(file.title)
        .swipeActions(edge: .trailing, allowsFullSwipe: true) {
            Button {
                $file.delete()
            } label: {
                Image(systemName: "trash")
            }
            .tint(.red)
        }
    }

    @ObservedObject var viewModel: FileRowViewModel
}

I don’t want to use the onDelete modifier because I want to custom the content of the swipe view (icon instead of text), I also want to add more custom swipe actions (like copy file for example).

Thanks a lot, I already browsed the web about deleting objects but the most common example is the one you give using onDelete modifier.

Hi @chlorofom ,

I’m afraid you’ve stumbled upon a Realm limitation. I tried to replicate your code and what seems to be happening is that SwiftUI keeps a “copy” of your data to make a diff once the source of truth changes. That’s how it can make moves, insertions and deletions on a List (and views for that matter).

So, when you delete an object from Realm, SwiftUI still has a reference to that object and tries to diff that object with the new set of objects. I think that’s what’s causing the crash. SwiftUI tries to use an delete/invalidated realm object.

Unfortunately, I haven’t found a simpler way to delete objects from realm aside the onDelete view modifier and a fetch with delete.

You can use the following:

try! realm.write {
  realm.delete(
    realm.objects(PrepFile.self).filter {
      $0.id == file.id
    }
  )
}

This is sort of ok as Realm will only fetch the desired object in memory and then delete it.

I hope this helps you.

1 Like

Thanks a lot Renan :slight_smile: I will try your solution or change my implementation then.

Hi Renan,

Good tutorial.
I would have loved to see something on updating an ObservedRealmObject as it appears that they(ObservedRealmObjects) are frozen and not so easy to update.

I also have a question about using the environment variable for the default realm.

You have the code…
@Environment(.realm) var realm
as well as the config variable …
.environment(.realmConfiguration, RealmMigrator.configuration)

How would these be changed to reference a named Realm such as “PotionMaster-2022”?
Is there a tutorial on creating these Environment variables?

Thank you

Hey @kct3937. Thank you so much. I’m glad you liked it.

Thank you for your suggestion. ObservedRealmObject objects are frozen by default but when you use $ on a property, Realm opens a safe transaction and already updates the values inside the database for you. You can learn more under Updating Objects and Updating Other Properties sections. If you must do it without a binding properties, you can also use the old API to update your objects.

let ingredient = Ingredient(value: ["id": sameID, propertiesToUpdate...])
try! realm.write {
  realm.add(ingredient, update: .modified)
}

As for using another instance of realm. You can’t set another Realm object (unless you create an environment value). But you can pass the path of the realm you want to use.

You can use Realm.Configuration to pass a fileURL of the Realm you want to open. You can, for example, store the file in the documents folder. Something like the following:

Realm.Configuration(
  fileURL: URL.documentsDirectory.appending(path: "PotionMaster-2022.realm"),
  schemaVersion: 1, 
  migrationBlock: migrationBlock
)

You can also just append a new path to the default realm configuration path.

You then use .environment(.realmConfiguration, RealmMigrator.configuration) to set this configuration.

I hope this helps you.