I am creating an app with the stored data hosted in CloudKit. When I perform a normal swipe to delete action on any of these list items, the deleteAlert()
displays (as it should). However, as long as the alert is displayed, the code continuously loops and creates an infinite number of blank Category
values, adding them to the list. At the same time, the alert doesn't allow you to tap on any of the buttons normally, but if you swipe your finger across the button, you can feel lots of short haptic feedback pulses (I suspect it's also looping through creating many overlapping alerts).
import SwiftUI
struct CategoryListView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(entity: Category.entity(), sortDescriptors: [], animation: .default)
private var categories: FetchedResults<Category>
// Passed value
var accountSelection: String
@State private var deletingItem = false
@State private var deleteIndexSet: IndexSet?
@State private var showingAddView = false
var body: some View {
List {
ForEach(categories) { category in
HStack {
Button(action: {
self.showingAddView.toggle()
}) {
Text("\(category.name ?? "")")
}
}
.alert(isPresented: $deletingItem, content: deleteAlert)
}
.onDelete { indexSet in
self.deletingItem = true
self.deleteIndexSet = indexSet
}
}
}
func deleteAlert() -> Alert {
var deletedCategory = Category(context: viewContext) // removing this line causes everything to work properly
try! deletedCategory = categories[deleteIndexSet?.first ?? 0]
return Alert(
title: Text("Delete \(deletedCategory.name ?? "nil")?"),
message: Text("Deleting \(deletedCategory.name ?? "nil") will not remove all entries from that category."), // TODO: make it so that if entry does not have a category, add it to a "miscellaneous" or "other" category
primaryButton: .cancel(),
secondaryButton: .destructive(Text("Delete"), action: {print("")})
)
}
}
The reason the view is constantly refreshing boils down to the way @FetchRequest
works (the array of CloudKit fetched items). Whenever the value of categories
changes (as with @State
and @ObservedObject
etc.), the view it is attached to refreshes. The following loop will repeat endlessly in this case:
Inside of deleteAlert()
, var deletedCategory = Category(context: viewContext)
creates a new Category
and immediately adds it to the current context (the list).
This causes the view to refresh as previously mentioned.
The value of $deletingItem
is still true, so another alert will display.
When an alert is displayed, it triggers the code inside of deleteAlert()
.
Rinse and repeat steps 1-4 infinitely.
TL;DR Don't create ManagedObjects
/ObservableObjects
in a View
/Body
(as @lorem ipsum also pointed out).