Deletes in Holiday Card Tracker

I’ve been working on a Greeting Card tracking app, in my spare time for years now, five to be exact. I may get it on the App Store one day, but primarily it is just used to track the cards my wife and I send to friends and family. I’ve re-written it a few times, going from UIKit to SwiftUI, and then improving the Swift code to do CoreData with CloudKit syncing. I am hoping to rewrite it sometime in the next year to fully SwiftData, but right now I am struggling with deleting cards correctly.

Conceptually, deleting a child object in a parent-child relationship in CoreData is not hard. You just have to delete the child and CoreData should handle the rest; however, I am showing all the children in a LazyVGrid, so that you can get a quick and easy overview of all the cards you have sent to a single recipient.

As you can see above, we have at least 6 cards visible at once, and each card will allow you to edit, view in detail, or delete. Each of these “events” is a separate SwiftUI View, and on top of it is the MenuOverlayView:

//
//  MenuOverlayView.swift
//  Card Tracker
//
//  Created by Michael Rowe on 4/16/22.
//  Copyright © 2022 Michael Rowe. All rights reserved.
//

import SwiftUI

struct MenuOverlayView: View {
    @Environment(\.managedObjectContext) var moc
    @Environment(\.presentationMode) var presentationMode

    @State var areYouSure: Bool = false
    @State var isEditActive: Bool = false
    @State var isCardActive: Bool = false

    private let blankCardFront = UIImage(contentsOfFile: "frontImage")
    private var iPhone = false
    private var event: Event
    private var recipient: Recipient

    init(recipient: Recipient, event: Event) {
        if UIDevice.current.userInterfaceIdiom == .phone {
            iPhone = true
        }
        self.recipient = recipient
        self.event = event
    }

    var body: some View {
        HStack {
            Spacer()
            NavigationLink {
                EditAnEvent(event: event, recipient: recipient)
            } label: {
                Image(systemName: "square.and.pencil")
                    .foregroundColor(.green)
                    .font(iPhone ? .caption : .title3)
            }
            NavigationLink {
                CardView(
                    cardImage: (event.cardFrontImage ?? blankCardFront)!,
                    event: event.event ?? "Unknown Event",
                    eventDate: event.eventDate! as Date)
            } label: {
                Image(systemName: "doc.text.image")
                    .foregroundColor(.green)
                    .font(iPhone ? .caption : .title3)
            }
            Button(action: {
                areYouSure.toggle()
            }, label: {
                Image(systemName: "trash")
                    .foregroundColor(.red)
                    .font(iPhone ? .caption : .title3)
            })
            .confirmationDialog("Are you Sure", isPresented: $areYouSure, titleVisibility: .visible) {
                Button("Yes", role: .destructive) {
                    withAnimation {
                        deleteEvent(event: event)
                    }
                }
                Button("No") {
                    withAnimation {
                    }
                } .keyboardShortcut(.defaultAction)
            }
        }
    }

    private func deleteEvent(event: Event) {
        let taskContext = moc
        taskContext.perform {
            taskContext.delete(event)
            do {
                try taskContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }
}

It’s a pretty simple view, with a menu overlay to Edit, View, or delete. The delete function just calls CoreData’s delete() and then save() methods on the current managedObjectContext. No problems here.

The problem is actually in the parent view…I won’t show all the code, but will show a simplified version of the LazyVGrid:

LazyVGrid(columns: gridLayout, alignment: .center, spacing: 5) {
                        ForEach(events, id: \.self) { event in
                            HStack {
                                VStack {
                                    Image(uiImage: (event.cardFrontImage ?? blankCardFront)!)
                                        .resizable()
                                        .aspectRatio(contentMode: .fit)
                                        .scaledToFit()
                                        .frame(width: iPhone ? 120 : 200, height: iPhone ? 120 : 200)
                                        .padding(.top, iPhone ? 2: 5)
                                    HStack {
                                        VStack {
                                            Text("\(event.event ?? "")")
                                                .foregroundColor(.green)
                                            Spacer()
                                            HStack {
                                                Text("\(event.eventDate ?? NSDate(), formatter: ViewEventsView.eventDateFormatter)")
                                                    .fixedSize()
                                                    .foregroundColor(.green)
                                                MenuOverlayView(recipient: recipient, event: event)
                                            }
                                        }
                                        .padding(iPhone ? 1 : 5)
                                        .font(iPhone ? .caption : .title3)
                                        .foregroundColor(.primary)
                                    }
                                }
                            }
                            .padding()
                            .frame(minWidth: iPhone ? 160 : 320, maxWidth: .infinity,
                                   minHeight: iPhone ? 160 : 320, maxHeight: .infinity)
                            .background(Color(UIColor.systemGroupedBackground))
                            .mask(RoundedRectangle(cornerRadius: 20))
                            .shadow(radius: 5)
                            .padding(iPhone ? 5: 10)
                        }

As you can see it just displays all the events, where each event is a card. Well since the delete method is in the MenuOverlayView, as defined above. So what happens is when the overlay delete occurs, I should pass back a message to reload the events in the LazyVGrid. But I don’t have that working, as I can’t figure out the correct way to do this.

At least this week, I was able to update my WastedTime in TestFlight to see if I have fixed an issue with the Gauge Complication not displaying the data correctly. So far so goo.