Using RealmSwift and SwiftUI, how can I look at a record in the view and change it simultaneously, without view poping out?

Viewed 40

I'm getting an object from Realm using @ObservedResults and then I need to modify the record in the same screen. Like adding items and deleting items, but I need this to happen without returning to previous NavigationView. And I need to update the view displaying the data as its updated.

It seems that @StateRealmObject should be the solution, but how can I designate a stored Realm object as a StateRealmObject? What would be a better approach?

Model:

class Order: Object, ObjectKeyIdentifiable {
    @objc dynamic var orderID : String = NSUUID().uuidString
    @objc dynamic var name : String = "No Name"
    let orderLineId = List<OrderLine>()
    .
    .
    .
    override static func primaryKey() -> String? {
        return "orderID"
      }
    
}

View: (simplified)

struct OrderView: View {
    var currentOrder : Order?
    @ObservedResults (Order.self) var allOrders
    var realm = try! Realm()

    init(orderId: String) {
        currentOrder = allOrders.filter("orderID ==\"\(orderId)\"")[0].thaw()
    }

    func deleteOrderLine(id: String, sku: String) {
        
        try! realm.write {
            let query = allOrderLines.filter("id == \"\(id)\" AND productSku == \"\(sku)\"")[0]
            realm.delete(query)
            
        }
    }

var body: some View {
//Here is where all order data is displayed. When deleting a line. The view pops out. I //need to change data and keep view, and save changes as they are made. 
//If delete line func is called, it works, but it pops the view.
    
    deleteOrderLine(id: String, sku: String)

   }
}

As per request, this is the parent navigation view, which I believe is the core of the problem.

struct OrderListView2: View {
    @ObservedResults (Order.self) var allOrders

    var body: some View {
        VStack{
            List{
            ForEach(allOrders.sorted(byKeyPath: "dateCreated",ascending: false), id: \.self) { order in
    NavigationLink(destination: OrderView(orderId: order.orderID)){
    Text(order.info...)
    }

}
1 Answers

The problem is that your allOrderLines collection is not frozen. This result set is live so when you delete an entry your SwiftUI view is displaying a result set that is older than your live collection.

Hence the RLMException for the index out of bounds. To solve this problem you must freeze that collection for the view then delete the desired object and then reload your frozen collection. To achieve this i would suggest you use a viewmodel.

class OrderViewModel: ObservableObject {
    @Published var orders: Results<Order>
    
    init() {
        let realm = try! Realm()
        self.orders = realm.objects(Order.self).freeze()
    }
    
    func deleteOrderLine(id: String, sku: String) {
        let realm = try! Realm()
        try! realm.write {
            let query = realm.objects(Order.self).filter("id == \"\(id)\" AND productSku == \"\(sku)\"")[0]
            realm.delete(query)
        }
        self.orders = realm.objects(Order.self).freeze()
    }
}

You can then use the @Published orders for your view. Initialize the viewmodel with @StateObject var viewModel = OrderViewModel()

Related