adamek
adamek

Reputation: 2644

How to use Realm with SwiftUI forms

Terminating app due to uncaught exception 'RLMException', reason: 'Attempting to modify object outside of a write transaction - call beginWriteTransaction on an RLMRealm instance first.'

All changes to a managed object (addition, modification and deletion) must be done within a write transaction. For example,

// Update an object with a transaction
try! realm.write {
    author.name = "Thomas Pynchon"
}

I can make a Realm sub-class conform to ObservableObject. However, I don't see how to make the realm properties updatable in SwiftUI. Realm property example below.

@objc dynamic var myName: String = "Adam"

Realm automagically sets up the schema based on @objc dynamic var. I don't see a way to get @Published on a realm property. SwiftUI will render a TextField, but crashes when the value is edited.

TextField("Quantity (\(shoppingItem.myUnit!.myName))", value: $shoppingItem.stdQty, formatter: basicFormat)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .keyboardType(.numbersAndPunctuation)

Is there any way to wrap SwiftUI state changes inside a Realm write transaction?

Upvotes: 1

Views: 1274

Answers (2)

Condrea David Emanuel
Condrea David Emanuel

Reputation: 111

Another way to do this is to use a Custom Binding, and when setting the property, open a transaction and save data to realm.

TextField("Quantity (\(shoppingItem.myUnit!.myName))",
          value: Binding(get: {shoppingItem.stdQty},
            set: {(value) in
                //if your state property is in a view model, all this code could be like viewModel.saveData(newMessage: value)
                let realm = try! Realm()
                try! realm.write {
                    shoppingItem.stdQty = value
                }
            }),
          formatter: basicFormat)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .keyboardType(.numbersAndPunctuation)

This will save to realm on every character inserted

Upvotes: 1

adamek
adamek

Reputation: 2644

Consider the Realm property, stdQty shown below. It can only be changed within a write transaction.

import RealmSwift
import Combine

class ShoppingItems: Object, ObservableObject    
    let objectWillChange = PassthroughSubject<Void, Never>()
    @objc dynamic var stdQty: Double = 1

You cannot bind stdQty without the error in the original question. However you can create a calculated variable that can be bound.

    var formQty: Double {
        get {
            return stdQty
        } 
        set {
            objectWillChange.send()
            if let sr = self.realm {
                try! sr.write {
                    stdQty = newValue
                }
            }
        }
    }

Binding the calculated variable works fine.

TextField("Quantity (\(shoppingItem.myUnit!.myName))", value: $shoppingItem.formQty, formatter: basicFormat)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                .keyboardType(.numbersAndPunctuation)

Answer limitation: objectWillChange is only triggered by the calculated variable. Changes in the form are reflected in the form. Changes in the realm don't trigger a Combine objectWillChange yet.

Upvotes: 0

Related Questions