'Error deleting records from a SwiftUI List and Realm

Has anyone been able to successfully integrate Realm with SwiftUI, especially deleting records/rows from a SwiftUI List? I have tried a few different methods but no matter what I do I get the same error. After reading some related threads I found out that other people have the same issue.

The following code successfully presents all of the items from Realm in a SwiftUI List, I can create new ones and they show up in the List as expected, my issues is when I try to delete records from the List by either manually pressing a button or by left-swiping to delete the selected row, I get an Index is out of bounds error.

Any idea what could be causing the error?

Here is my code:

Realm Model

class Dog: Object {
    @objc dynamic var name = ""
    @objc dynamic var age = 0
    @objc dynamic var createdAt = NSDate()

    @objc dynamic var userID = UUID().uuidString
    override static func primaryKey() -> String? {
        return "userID"
    }
}

SwiftUI Code

class BindableResults<Element>: ObservableObject where Element: RealmSwift.RealmCollectionValue {
    var results: Results<Element>

    private var token: NotificationToken!

    init(results: Results<Element>) {
        self.results = results
        lateInit()
    }
    func lateInit() {
        token = results.observe { [weak self] _ in
            self?.objectWillChange.send()
        }
    }
    deinit {
        token.invalidate()
    }
}

struct DogRow: View {
    var dog = Dog()
    var body: some View {
        HStack {
            Text(dog.name)
            Text("\(dog.age)")
        }
    }
}


struct ContentView : View {

    @ObservedObject var dogs = BindableResults(results: try! Realm().objects(Dog.self))

    var body: some View {
        VStack{

        List{
            ForEach(dogs.results, id: \.name) { dog in
                DogRow(dog: dog)
            }.onDelete(perform: deleteRow )
        }

            Button(action: {
                try! realm.write {
                    realm.delete(self.dogs.results[0])
                }
            }){
                Text("Delete User")
            }
        }
    }

    private func deleteRow(with indexSet: IndexSet){
        indexSet.forEach ({ index in
            try! realm.write {
                realm.delete(self.dogs.results[index])
            }
        })
    }
}

Error

Terminating app due to uncaught exception ‘RLMException’, reason: ‘Index 23 is out of bounds (must be less than 23).’

Of course, the 23 changes depending on how many items are in the Realm database, in this case, I had 24 records when I swiped and tapped the delete button.

FYI - The error points to the AppDelegate file with a Thread 1: signal SIGABRT.



Solution 1:[1]

Here is an example of how i do this. This is without realm operations but i hope u get the idea where you can put the realm stuff. (I also almost never use the realm objects directly but instead convert them to structs or classes.)

import Foundation
import Realm
import Combine
import SwiftUI

struct dogs: Hashable {
  let name: String
}

class RealmObserverModel: ObservableObject {

  var didChange = PassthroughSubject<Void, Never>()
  @Published var dogsList: [dogs] = [dogs(name: "Dog 1"), dogs(name: "Dog 2")]

  // get your realm objects here and set it to
  // the @Publsished var
  func getDogs() {
    let count = dogsList.count + 1
    dogsList.append(dogs(name: "Dog \(count)"))
  }

  // get your realm objects here and set it to
  // the @Publsished var
  func deletetDogs() {
   _ = dogsList.popLast()
  }
}

/// Master View
struct DogView: View {
  @EnvironmentObject var observer: RealmObserverModel

  var body: some View {

    VStack{
      DogsListView(dogsList: $observer.dogsList)

      HStack{
      Button(action: {
        self.observer.getDogs()
      }) {
      Text("Get more dogs")
      }

        Button(action: {
          self.observer.deletetDogs()
        }) {
        Text("Delete dogs")
        }
      }
    }
  }
}

// List Subview wiht Binding
struct DogsListView: View {
  @Binding var dogsList: [dogs]

  var body: some View {
    VStack{
      List{
        ForEach(dogsList, id:\.self) { dog in
          Text("\(dog.name)")
        }
      }
    }
  }
}

struct DogView_Previews: PreviewProvider {
  static var previews: some View {
    DogView().environmentObject(RealmObserverModel())
  }
}

Solution 2:[2]

Not a great solution but my work around was copying each realm result to a local object/array. I updated my Lists/Views to use the realmLocalData instead of the data returned from the realm object itself.

class ContentViewController: ObservableObject {
  private var realmLocalData: [ScheduleModel] = [ScheduleModel]()
  private let realm = try! Realm()

  func updateData() {
    realmLocalData.removeAll()
    let predicate = NSPredicate(format: "dateIndex >= %@ && dateIndex <= %@", argumentArray: [startDate, endDate])
    let data = self.realm.objects(MonthScheduleModel.self).filter(predicate)
 
    for obj in data {
      realmLocalData.append(ScheduleModel(realmObj: obj))
    }
  }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Peter Pohlmann
Solution 2 Soccrfoot