0

I hope you are having a more pleasant evening than mine!

So as I mentioned in the title, my for each loop crashes whenever I try to remove an item from the original list with a binding. I did some research and the problem is that for each generates a view with an id but when you delete the item in your child view it can't find the contents and crashes. Returns 'Thread 1: Fatal error: Index out of range'. I can fix the issue by declaring a @State var instead of @Binding, which really works! However, I have more than a delete button in my child view and if I don't use a binding declaration, changes made don't reflect on the main view. I don't wanna give up on neither the delete button nor buttons. Is there way to keep all of them in my childview?

Mainview declarations;

    struct ContentView: View {
    @ObservedObject var superReminders = SuperReminders()
    @State var superReminder = SuperReminder()}

My list View;

           List{
                    ForEach(superReminders.reminderlist.indices, id: \.self) { index in
                        NavigationLink(destination: DetailedRemView(superReminder : self.$superReminders.reminderlist[index] ).environmentObject(superReminders)) {
                            squareImageView(superReminder : self.$superReminders.reminderlist[index]).environmentObject(superReminders).environmentObject(superReminders)
                        }.listRowBackground(Color.clear)
                    }.onDelete { indexSet in
                        superReminders.reminderlist.remove(atOffsets: indexSet)}
                }

Childview declarations;


import SwiftUI
struct DetailedRemView: View {
    var dateFormatter: DateFormatter {
          let formatter = DateFormatter()
        formatter.dateFormat = "EE, MMM d, YYYY"
          return formatter
      }
    @State public var showingDetail = false
    @Environment(\.colorScheme) var colorScheme: ColorScheme
    @State private var deleteReminderAlert = false
    @EnvironmentObject var superReminders : SuperReminders
    @Environment(\.presentationMode) var presentationMode
    @Binding var superReminder : SuperReminder
    @State private var showDialog = false
    @State var animate = false
    var body: some View {
            VStack{
                HStack(alignment: .center){
                    Text(superReminder.remdate)
                        .font(.title)
                        .multilineTextAlignment(.leading)
                        .padding(.leading)
                        .frame(minWidth: 100,maxWidth: .infinity, maxHeight: 50)
                    Spacer()
                    Button(action: {
                        self.showDialog.toggle()
                    }, label: {
                        ZStack{
                            RoundedRectangle(cornerRadius: 10)
                                .fill(Color.blue)
                                .frame(width: 80, height: 35)
                            HStack{
                                Text("Edit")
                                    .foregroundColor(.white)
                                    .multilineTextAlignment(.center)
                                    .cornerRadius(8)
                                Image(systemName: "pencil")
                                    .foregroundColor(.white)
                            }
                        }
                        .shadow(color:Color.gray.opacity(0.3), radius: 3, x: 3, y: 3)
                        .padding(.leading)
                                .alert(isPresented: $showDialog,
                                TextAlert(title: "Edit reminder title",
                                          message: "Enter a new title or dissmis.", placeholder: superReminder.remdate,
                                              keyboardType: .default) { result in
                                  if let text = result {
                                    if text != "" {
                                        superReminder.remdate = text }
                                    else{}
                                  } else {
                                  }
                                })
                    })
                    .padding(.leading)
                }
                .frame(minWidth: 100, maxWidth: /*@START_MENU_TOKEN@*/.infinity/*@END_MENU_TOKEN@*/, maxHeight: 50, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
                .padding(.vertical, -10)
                ZStack(alignment: .topTrailing){
                    Image(superReminder.image)
                        .resizable()
                        .aspectRatio(contentMode: .fill)
                        .frame(minWidth: 0,maxWidth: .infinity,minHeight: 200,maxHeight: .infinity)
                        .saturation(superReminder.pastreminder ? 0.1 : 1)
                        .clipShape(Rectangle())
                            .cornerRadius(10)
                        .padding(.all)
                        .pinchToZoom()
                    HStack{
                        Text(superReminder.dateactual, formatter: dateFormatter)
                            .foregroundColor(.white)
                            .frame(width: 180, height: 30, alignment: /*@START_MENU_TOKEN@*/.center/*@END_MENU_TOKEN@*/)
                            .background( superReminder.pastreminder ? Color.gray : Color.lightlygreen)
                            .cornerRadius(8)
                            .animation(/*@START_MENU_TOKEN@*/.easeIn/*@END_MENU_TOKEN@*/)
                        if superReminder.pastreminder == true {
                        ZStack{
                            RoundedRectangle(cornerRadius: 8)
                                .fill(Color.black)
                                .frame(width: 30, height: 30)
                            Image(systemName: "moon.zzz")
                                .foregroundColor(.white)
                        }.offset(x: animate ?  -3 : 0)
                        .onAppear(perform: {
                            shake()
                        })
                        }
                        else{}
                        }
                    .zIndex(0)
                    .offset(x: -10, y: 10)
                    .padding()
                    }
                .zIndex(1)
                .shadow(color: Color.gray.opacity(0.4), radius: 3, x: 1, y: 2)
                HStack{
                    Button(action: {
                        self.showingDetail.toggle()
                    }){
                    ZStack{
                        RoundedRectangle(cornerRadius: 10)
                            .fill(Color.lightlygreen)
                            .frame(width: 140, height: 40)
                        HStack{
                            Text("Reschedule")
                                .foregroundColor(.white)
                                .multilineTextAlignment(.center)
                                .cornerRadius(8)
                            Image(systemName: "calendar")
                                .foregroundColor(.white)
                        }
                    }
                    .shadow(color:Color.gray.opacity(0.3), radius: 3, x: 3, y: 3)
                    .padding(.all, 4.0)
                    }
                        .sheet(isPresented: $showingDetail, content :{
                                remdatepicker(isPresented: self.$showingDetail, superReminder: $superReminder)})
                    Button(action: {
                        if superReminder.pastreminder == true {
                            superReminder.pastreminder = false
                        }
                        else if superReminder.pastreminder == false{
                            superReminder.pastreminder = true
                        }
                    }, label: {
                        ZStack{
                            RoundedRectangle(cornerRadius: 10)
                                .fill(superReminder.pastreminder == true ? Color.lightlyblue : Color.gray)
                                .frame(width: 100, height: 40)
                            HStack{
                                Text(superReminder.pastreminder == true ? "Activate" : "Silence")
                                    .foregroundColor(.white)
                                    .multilineTextAlignment(.center)
                                    .cornerRadius(8)
                                Image(systemName: superReminder.pastreminder == true ? "checkmark.circle" : "moon.zzz")
                                    .foregroundColor(.white)
                                    }

                        }
                        .shadow(color:Color.gray.opacity(0.3), radius: 3, x: 3, y: 3)
                        .padding(.all, 4.0)
                    })
                    Button(action: {
                        self.deleteReminderAlert.toggle()
                    }, label: {
                        ZStack{
                            RoundedRectangle(cornerRadius: 10)
                                .fill(Color(.red))
                                .frame(width: 40, height: 40)
                            HStack{
                                Image(systemName: "trash")
                                    .foregroundColor(.white)
                            }
                        }
                        .shadow(color:Color.gray.opacity(0.3), radius: 3, x: 3, y: 3)
                        .padding(.all, 4.0)
                        })
                }.padding(.bottom, 20)
                .alert(isPresented: $deleteReminderAlert){
                        Alert(
                          title: Text("Are you sure?"),
                          message: Text("Do you want to delete this reminder?"),
                            primaryButton: .destructive(Text("Yes"), action: {
                                superReminders.remove(superReminder: superReminder)
                                self.presentationMode.wrappedValue.dismiss()
                            }),
                          secondaryButton: .cancel(Text("No"))
                )
            }
        }
    }
    func shake()  {
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            withAnimation(Animation.default.repeatCount(6).speed(7)){
                animate.toggle()}}}
}

Class and List;


import SwiftUI

struct SuperReminder: Identifiable, Codable, Equatable {
    var id = UUID()
    var remdate = ""
    var dateactual = Date.init()
    var image  = "New1"
    var pastreminder = false
}

class SuperReminders: ObservableObject {
    @Published var reminderlist: [SuperReminder]
    
    init() {
        self.reminderlist = [
        ]
    }
    func add(superReminder: SuperReminder) {
            reminderlist.append(superReminder)
        }

        func remove(superReminder: SuperReminder) {
            if let index = reminderlist.firstIndex(of: superReminder) {
                reminderlist.remove(at: index)
            }
        }
}

1 Answers1

0

This answer is similar to Accessing and manipulating array item in an EnvironmentObject


Loop over superReminders.reminderlist since SuperReminder: Identifiable, Codable, Equatable.

ForEach(superReminders.reminderlist) { superReminder in
  NavigationLink(destination: DetailedRemView(superReminders: superReminders,
                                              superReminder: superReminder)) {
    -----
  }
}

In DetailedRemView, do the following:

struct DetailedRemView: View {

    @ObservedObject var superReminders : SuperReminders
    
    var superReminder : SuperReminder
    
    // find index of current superReminder   
    var indexOfReminder: Int? {
        superReminders.reminderlist.firstIndex {$0 == superReminder}
    }
    
    var body: some View {
        // Unwrap indexOfReminder
        if let index = indexOfReminder {
            VStack {
               ------ 
            }
        }
    }


    ----

}

Use superReminders.reminderlist[index] in DetailRemView whereever you need to update superReminder.

superReminders.reminderlist[index].pastreminder = false
mahan
  • 12,366
  • 5
  • 48
  • 83
  • Actually, I was doing this before and this was working great for viewing the reminders and deleting them. However, when I edited the properties of an object inside the child view( let's say I changed the date or name), changes revert back to original values after going back to the parent view. I think that declaring a @State var in child view just copies the values and creates a temporary object just for viewing purposes. Please correct me if I'm wrong, I have just started coding 1 month ago. – Kalayci Musa Feb 04 '21 at 14:29
  • @KalayciMusa updated my answer. Did not notice that you edit superReminder. – mahan Feb 04 '21 at 15:59
  • Thank you so much! I can't appreciate your help enough right now! It solved all of my problems. One small drawback is that when you edit something I think it refreshes the whole view which is visible. The whole view blacks out for like a millisecond. Is there a way to eliminate that as well? Or am I asking too much? – Kalayci Musa Feb 05 '21 at 13:20
  • 1
    I found out that if statement causes the delay. However, if you find the index using this code `var indexOfReminder: Int { superReminders.reminderlist.firstIndex (where: {$0.id == superReminder.id})! }` you don't have to set up an if statement inside your view. Anyway thanks again and a great lesson learned! – Kalayci Musa Feb 05 '21 at 14:01
  • I can not really test your code becasue there are some custom views. I don't think that if statements causes the delay. It was just an extra check. Your solution is correct. @KalayciMusa – mahan Feb 06 '21 at 14:31