I've changed my app from a simple list view of Core Data to a LazyVGrid. When I did that, I lost the ability to easily delete an entry in the list, so I moved the delete function to the details view. My hope was to provide both Edit and Delete functions from the detail view; however, when I delete an entry, it now crashed the app, before processing the self.presentationMode.wrappedValue.dismiss() . Can any one take a look at this code and let me know how to handle this correctly?
--- LazyVGrid ----
import SwiftUI
import CoreData
import MapKit
enum NavBarItemChoosen: Identifiable {
case newCard
var id: Int {
hashValue
}
}
struct ViewEventsView: View {
@Environment(\.managedObjectContext) var moc
@Environment(\.presentationMode) var presentationMode
@FetchRequest private var events: FetchedResults<Event>
private var blankCardFront = UIImage(contentsOfFile: "frontImage")
private var recipient: Recipient
@State var newEvent = false
@State var frontView = false
@State var backView = false
@State var frontShown = true
@State private var frontImageShown: UIImage?
@State var navBarItemChoosen: NavBarItemChoosen?
private var gridLayout: [GridItem]
@State var isEditing = false
@State var region: MKCoordinateRegion?
static let eventDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
return formatter
}()
init(recipient: Recipient) {
request.predicate = NSPredicate(format: "%K == %@", #keyPath(Event.recipient), recipient)
_events = FetchRequest<Event>(fetchRequest: request)
if UIDevice.current.userInterfaceIdiom != .phone {
self.gridLayout = [GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())]
} else {
self.gridLayout = [GridItem(.flexible()), GridItem(.flexible())]
}
}
var body: some View {
GeometryReader { geo in
VStack {
HStack {
if let region = region {
MapView(region: region)
.frame(width: geo.size.width * 0.3, height: geo.size.height * 0.2)
.padding([.leading, .trailing], 10 )
AddressView(recipient: recipient)
}
Spacer()
.onAppear {
// swiftlint:disable:next line_length
let addressString = String("\(recipient.addressLine1 ?? "") \(recipient.city ?? "") \(recipient.state ?? "") \(recipient.zip ?? "") \(recipient.country ?? "")")
getLocation(from: addressString) { coordinates in
if let coordinates = coordinates {
// swiftlint:disable:next line_length
self.region = MKCoordinateRegion(center: coordinates, span: MKCoordinateSpan(latitudeDelta: 0.005, longitudeDelta: 0.005))
}
}
}
}
ScrollView {
LazyVGrid(columns: gridLayout, alignment: .center, spacing: 1) {
ForEach(events, id: \.self) { event in
NavigationLink(destination: ViewAnEventView(event: event, recipient: recipient)) {
HStack {
VStack {
ZStack {
Spacer()
Image(uiImage: (event.cardFrontImage ?? blankCardFront)!)
.resizable()
.aspectRatio(contentMode: .fit)
.ignoresSafeArea(edges: [.vertical, .bottom])
HStack {
VStack {
Spacer()
Text("\(event.event ?? "")")
// swiftlint:disable:next line_length
Text("\(event.eventDate ?? NSDate(), formatter: ViewEventsView.eventDateFormatter)")
Spacer()
}
.padding(10)
.font(.title)
.foregroundColor(.white)
.shadow(color: .black, radius: 2.0)
}
}
}
}
.frame(height: geo.size.width * 0.3)
.contextMenu {
Button {
print("Edit pressed for \(event) and \(recipient)")
_ = ViewAnEventView(event: event, recipient: recipient)
} label: {
Label("Edit", systemImage: "square.and.pencil")
.foregroundColor(.green)
}
}
}
}
}
}
.navigationTitle("\(recipient.firstName ?? "no first name") \(recipient.lastName ?? "no last name")")
.navigationBarItems(trailing:
HStack {
Button(action: {
navBarItemChoosen = .newCard
}, label: {
Image(systemName: "plus.circle.fill")
.foregroundColor(.green)
})
})
}
.sheet(item: $navBarItemChoosen ) { item in
switch item {
case .newCard:
AddNewCardView(recipient: recipient)
}
}
}
}
func getLocation(from address: String, completion: @escaping (_ location: CLLocationCoordinate2D?) -> Void) {
let geocoder = CLGeocoder()
geocoder.geocodeAddressString(address) { (placemarks, _) in
guard let placemarks = placemarks,
let location = placemarks.first?.location?.coordinate else {
completion(nil)
return
}
completion(location)
}
}
}
struct ViewEventsView_Previews: PreviewProvider {
static var previews: some View {
ViewEventsView(recipient: Recipient())
}
}
--- Details View ---
import SwiftUI
struct ViewAnEventView: View {
enum NavBarItemChoosen: Identifiable {
case editEvent
var id: Int {
hashValue
}
}
@Environment(\.presentationMode) var presentationMode
@Environment(\.managedObjectContext) var moc
@State private var zoomed = false
private var event: Event
private var recipient: Recipient
private var blankCardFront = UIImage(contentsOfFile: "frontImage")
@State var navBarItemChoosen: NavBarItemChoosen?
enum ShowCardView: Identifiable {
case front, edit
var id: Int {
hashValue
}
}
@State var showCardView: ShowCardView?
static let eventDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
return formatter
}()
init(event: Event, recipient: Recipient) {
let navBarApperance = UINavigationBarAppearance()
navBarApperance.largeTitleTextAttributes = [
.foregroundColor: UIColor.systemGreen,
.font: UIFont(name: "ArialRoundedMTBold", size: 35)!
]
navBarApperance.titleTextAttributes = [
.foregroundColor: UIColor.systemGreen,
.font: UIFont(name: "ArialRoundedMTBold", size: 15)!
]
UINavigationBar.appearance().standardAppearance = navBarApperance
UINavigationBar.appearance().scrollEdgeAppearance = navBarApperance
UINavigationBar.appearance().compactAppearance = navBarApperance
self.recipient = recipient
self.event = event
}
var body: some View {
VStack {
HStack {
Text("\(event.event ?? "no event") - \(event.eventDate!, formatter: Self.eventDateFormatter)") // <---- CRASH
.font(.title)
.foregroundColor(.green)
Spacer()
Button(action: {
showCardView = .edit
}, label: {
Image(systemName: "square.and.pencil")
.font(.title)
.foregroundColor(.green)
})
Button {
self.presentationMode.wrappedValue.dismiss()
deleteCard(event: event)
} label: {
Image(systemName: "trash")
.font(.title)
.foregroundColor(.red)
}
}
.padding([.leading, .trailing], 10 )
Image(uiImage: (event.cardFrontImage ?? blankCardFront)!) // <-- Second crash if moved
.resizable()
.aspectRatio(contentMode: zoomed ? .fill : .fit)
Spacer()
.sheet(item: $showCardView) { item in
switch item {
case .front:
// This is no longer used...
// swiftlint:disable:next line_length
CardView(cardImage: (event.cardFrontImage ?? blankCardFront)!, event: event.event ?? "", eventDate: event.eventDate! as Date)
case .edit:
EditAnEvent(event: event, recipient: recipient)
}
}
}
}
func deleteCard(event: Event) {
print("Delete the card \(event)")
moc.delete(event)
do {
try moc.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
struct ViewAnEventView_Previews: PreviewProvider {
static var previews: some View {
ViewAnEventView(event: Event(), recipient: Recipient())
}
}
The crash happens at the line indicated above with the following message - /ViewAnEventView.swift:65: Fatal error: Unexpectedly found nil while unwrapping an Optional value.
I have tried to change the unwrapping to a default value, but it just moved the error to the Image line (which also crashes).