1
votes

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?

2
I'd use the MVVM design pattern, create a View Model for each View and Row, then map the corresponding realm properties to bindings defined in your View Model. - Rob
There's not enough code to identify the issue. Editing a UITextField element is unrelated to modifying a Realm Object outside a write transaction. In other words, a text field is a container that holds a string, and it's just a string, not a Realm object. What should happen is when the string is modified, that would trigger an event within the code of that modification, to where the Realm object could be updated. Did you bind the field to a Realm Object property? Please review the following guide How to create a Minimal, Complete, and Verifiable example - Jay
Thanks for the MCVE link. I was hoping for an easy way to wrap a write transaction inside a Binding<String> somehow. Instead it looks like I'm creating several ViewModels or Controllers. - adamek

2 Answers

0
votes

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.

0
votes

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