Search code examples
swiftswiftuirealm

Impossible to delete all element on Realm using a List on SwiftUi


I have a list displaying all my elements. I want to add a button deleting all my elements from my list but also from my Realm Database. I have well understood that I cannot call a Realm Object after deleting it however I cannot understood why when I click on my "Delete all" button I got this error : Object has been deleted or invalidated Here is my code : SettingsView :

import SwiftUI

struct SettingsView: View {
    
    @Binding var showSetting: Bool
    
    
    @State var allAction01: [Action] = []
    @State var allAction12: [Action] = []
    
    init(showSetting: Binding<Bool>) {
        self._showSetting = showSetting
    }
    
    var body: some View {
        NavigationStack {
                List{
                    
                    Section{
                    
                        Button(action: deleteAll){
                            Text("Delete")
                                .foregroundStyle(.red)
                        }
                            .buttonStyle(.plain)
                    } // Section
                    
                    Section {
                        Text("Liste des actions")
                            .fontWeight(.bold)
                            .font(.system(size: 20))
                    } // Section
                    
                    Section {
                        ForEach(allAction01, id: \.id) { action in
                            Text(action.text)
                        }
                        .onDelete(perform: delete)
                
                    
                    Section {
                        ForEach(allAction12, id: \.id) { action in
                            Text(action.text)
                        }

                } // List
                .onAppear(perform: updateAction)                    
        } // NavigationStack
    } // var body: some View
    
    private func delete(indexSet: IndexSet){
        indexSet.forEach { index in
            DataManager.deleteAction(action: allAction01[index])
        }
        updateAction()
    } // private func delete(indexSet: IndexSet)
    

    private func deleteAll(){
        DataManager.deleteAllActions()
        updateAction()
    }
    
    private func updateAction(){
        allAction01 = DataManager.getActionBetween(inf: 0, sup: 1)
        allAction12 = DataManager.getActionBetween(inf: 1, sup: 2)
   
    }
} // struct SettingsView: View

And here is my

DataManager.deleteAllActions()

    static func deleteAllActions() {
        do {
            try realm.write(){
                let actionToDelete = realm.objects(Action.self)
                realm.delete(actionToDelete)
            }
        } catch {
            print("Error : \(error)")
        }
    }

If you have any idea il would really help me !

PS : I display my SettingsView as a Sheet overlaying another view but it doesn't cause any problems when deleting only one action.

I try to delete all my action, I wish to have an empty BDD and list after this


Solution

  • The core issue is the use of an Array to hold your Realm data.

    While deleting objects from Realm removes the underlying object, which also invalidates the object in the array, it does NOT remove the index from the array.

    As an example, suppose the following; There's a PersonClass object and 5 persons are stored in Realm.

    let personResults = realm.objects(PersonClass.self) //get 5 persons
    let personArray = Array(personResults) //instantiate an array with 5 persons
    
    print(personResults.count) //prints 5
    print(personArray.count) // also prints 5
    
    try! realm.write {
        realm.delete(personResults) //delete the persons from Realm
    }
    
    print(personResults.count) //prints 0 as there are no persons in Realm
    print(personArray.count) //print 5 since 5 indexes exist, containing nil objects
    

    There are multiple approaches to resolving this

    1. When removing an object from Realm, also remove it from the array

    2. Use either @ObservedRealmObject for working with a specific object or in this use case, use the @ObservedResults property wrapper, which is defined as:

    Observe a collection of query results. You can perform a quick write to an ObservedResults collection, and the view automatically updates itself when the observed query changes. For example, you can remove a dog from an observed list of dogs using onDelete.

    Replacing the current vars with something along these lines will fix the issue

    @ObservedResults(Action.self) var actions01
    //@State var allAction12: [Action] = []
    
    init(showSetting: Binding<Bool>) {
       self._showSetting = showSetting
    }
    
    var body: some View {
       NavigationStack {
          List {
             //use actions01 here for the UI
         
    

    and use the action01 as the Lists datasource. Then, when deleting all actions, the view will update automatically as actions01 will always reflect the current state of the underlying data - an object is removed, it's removed from actions 01 and the UI updates accordingly.

    Results can be treated very much like a read only array; iterate over it, access a specific index etc. It cannot be written to - write to Realm to update the results instead