marwan37
marwan37

Reputation: 100

Fatal error: Index out of range, while deleting item from Firebase inside a List in SwiftUI

I've tried all kinds of solutions proposed here and elsewhere (eg, adding extensions to Collection, [safe] element iteration, etc.), but nothing has worked unfortunately. I've also tried changing "last!" to "first!" in the onDelete code, but that causes the same issue although surprisingly it did work for others in the other threads I've seen. I should note that this only started to happen once I combined CoreData along with Firebase in my app... Your help would be tremendously appreciated. Thanks in advance.

Class used as an environment object and to initiate Firebase:

class DayData: ObservableObject, Identifiable  {
    @Published var date = Date()
    @Published var completed = false
    @Published var percentage: Double = 0.0
    @Published var liquids: Double = 0
    
    var allData : [DayCellModel] = []
    
        let dbRef = Firestore.firestore()
        init() {
            readAllData()
        }
        func readAllData(){
            
            
            dbRef.collection("keanu").addSnapshotListener{ (snap, err) in

                guard let docs = snap else { return }

               
                    self.allData = docs.documents.compactMap({ (doc) -> DayCellModel? in
                        return try! doc.data(as: DayCellModel.self)
                    })
            } 

DayCellModel struct used only for Firebase:

struct DayCellModel : Identifiable, Codable {

    @DocumentID var id: String?
    var date : Date = Date()
    var completed : Bool = false
    var percentage : Double = 0.0
    var liquids: Int = 0
}

Data model class used only for CoreData:

extension NSDayData {

    @nonobjc public class func getDayData() -> NSFetchRequest<NSDayData> {
        let request:NSFetchRequest<NSDayData> = NSDayData.fetchRequest() as! NSFetchRequest<NSDayData>
        
        let sortDescriptor = NSSortDescriptor(key: "date", ascending: true)
        request.sortDescriptors = [sortDescriptor]
        
        return request
    }

    @NSManaged public var completed: Bool
    @NSManaged public var id: UUID?
    @NSManaged public var liquids: Double
    @NSManaged public var percentage: Double
    @NSManaged public var date: Date?

}

extension NSDayData : Identifiable {

}

Struct containing the code to my List and onDelete code:

    struct ListView: View {
        @Environment(\.managedObjectContext) var moc
        @FetchRequest(fetchRequest : NSDayData.getDayData()) var dayItems:FetchedResults<NSDayData>
        @EnvironmentObject var data : DayData
        
    
        var body: some View {
           NavigationView{
    
                        VStack(spacing: 10){
                        HStack{
                                    Text("Date")
                                    Text("Completion")
                                    Text("Liquids")
                                      
                                }.font(.caption)
                            
                            List{
                              ForEach(dayItems, id: \.id) { collection in
                                HStack{
    
                                        Text("\(collection.date?.string(format: self.dateFormat) ?? Date().string(format: self.dateFormat))")
                             
    
                                        Text(collection.percentage > 0.95 ? "100%" : "\(Int(collection.percentage * 100))")
    
                                   
                                        Text("\(Int(collection.liquids))oz")
                                
                                    
                                }.font(.system(size: 10, weight: .light))
                          
     
                              }.onDelete{ (index) in
                                    let db = Firestore.firestore()
                                
                                    db.collection("keanu")
                                        .document(self.data.allData[index.last!].id!)
                                        .delete { (err) in
    
                                        if err != nil{
    
                                            print((err?.localizedDescription)!)
                                            return
                                        }
    
                                    }
                                    self.deleteItems(at: index)
                              }
                        }
                    }
func deleteItems(at offsets: IndexSet) {
        withAnimation{
            offsets.map { dayItems[$0] }.forEach(moc.delete)
            saveMoc()
            data.allData.remove(atOffsets: offsets)
        }
    }

I have another view where I add new Data and save it to Firebase and CoreData like so:

if self.newCollection {
                let dayData = NSDayData(context: self.moc)
                dayData.id = UUID()
                dayData.date = self.data.date
                dayData.completed = self.data.percentage > 0.95
                dayData.percentage = self.data.percentage
                dayData.liquids = self.data.liquids

                do {
                    try moc.save()
                } catch {
                    let error = error as NSError
                    fatalError("Unresolved Error: \(error)")
                }
                
                let db = Firestore.firestore()
                db.collection("keanu").document()
                    .setData(
                        [
                        "date": self.data.date,
                         "completed": self.data.percentage > 0.95,
                         "percentage": self.data.percentage,
                         "liquids": self.data.liquids
                        ]) { (err) in
                        
                        if err != nil{
                            
                            print((err?.localizedDescription)!)
                            return
                        }
                
                    }

Upvotes: 0

Views: 139

Answers (1)

marwan37
marwan37

Reputation: 100

Ok so I finally figured it out after watching some great videos by kavsoft on youtube. For those that may run into similar issues, here's my solution:

  1. Don't use the onDelete{} function on the ForEach. Instead create a button to delete items so you don't have to deal with indexes.

  2. If working with CoreData & Firebase, create a string variable that will be equivalent to both the struct and core data class (see below).

  3. In the Editing View (Add New view, etc.), I created the following:

@Binding var docID: String

  1. Inside the list view, create an @State for the binding var for docID

    @State var docID = ""

Then when I present the editing view from my list it reads as follows:

   .sheet(isPresented: $show, content: {
                EditView(docID: $docID, show: self.$show newEntry: true)
                 
                        .environment(\.managedObjectContext, self.moc)
                 })
  1. I added a Button that deletes the entry from Firebase, CoreData and the list simultaneously. This is nested inside the ForEach so you get a "minus" symbol next to each item in the list:

    if self.remove{
    
         VStack{
              Button(action: {
                 let db = Firestore.firestore()
    
    
     db.collection("myCollection").document(docID).delete()
    
     moc.delete(collection)
    
     saveMoc()
      }
    
    
     }) {
    
           Image(systemName: "minus.circle.fill")
    
          }.buttonStyle(BorderlessButtonStyle())
       }
    

5.2. I also have a navigation bar button that triggers the "-" symbols to appear

var deleteButton : some View {
        Button(action: {
            
            self.remove.toggle()
            
        }) {
           
            Image(systemName: self.remove ? "xmark.circle" : 
    "trash")
                
        }

                
  1. Lastly, when saving the entry I set CoreData property:

        let dayData = NSDayData(context: self.moc)
         dayData.id = self.docID
    

Firebase property:

let db = Firestore.firestore()
                db.collection("yourCollection").document(self.docID)
                    .setData(
                        [
                         "id": self.docID,
...

Upvotes: 1

Related Questions