I have a custom list I built to show historical searches that pulls from a Core Data store. The custom list is essentially a VStack with a ForEach loop. I had to rebuild the list as custom, instead of the stock ListView, because the parent view is a scrollview and the stock ListView disappears when embedded in a scrollview.
I've implemented a Delete function that successfully allows a user to delete a historical entry, but the list does not update. if I click away from the view or quit the app and come back to the view, it displays correctly. Is there a way to force refresh the view?
When the Custom List View first loads it gets the history data from the ViewModel and creates the list.
ViewModel:
import CoreData
class HistoryListViewModel: ObservableObject {
@Published var histories = [HistoryViewModel]()
func getSearchedQueries() {
_ = CoreDataManager.shared.getSavedQueries()
let request: NSFetchRequest<History> = History.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(key: "searchDate", ascending: false)]
let fetchResultsController: NSFetchedResultsController<History> = NSFetchedResultsController(fetchRequest: request, managedObjectContext: CoreDataManager.shared.persistentContainer.viewContext, sectionNameKeyPath: nil, cacheName: nil)
try? fetchResultsController.performFetch()
DispatchQueue.main.async {
self.histories = (fetchResultsController.fetchedObjects ?? []).map(HistoryViewModel.init)
}
}
func deleteQuery(history: HistoryViewModel) {
let history = CoreDataManager.shared.getQueryById(id: history.id)
if let history = history {
CoreDataManager.shared.deleteQuery(history)
}
}
func deleteAll() {
CoreDataManager.shared.deleteAllQueries()
}
}
Here's the History View
struct HistoryView: View {
// MARK: - PROPERTIES
@StateObject private var viewModel = HistoryListViewModel()
@State var prepareForDeletion: Bool = false
// MARK: - BODY
var body: some View {
VStack {
ForEach(viewModel.histories, id: \.id) { history in
NavigationLink(
destination: HeroDetailView(heroId: history.characterId),
label: {
HistoryRow(history: history, prepToDelete: $prepareForDeletion)
.padding(.vertical, 3)
})
.buttonStyle(.plain)
}
}
.onAppear(perform: {
viewModel.getSearchedQueries()
})
.toolbar {
#if os(iOS)
ToolbarItem(placement: .navigationBarTrailing) {
Button(prepareForDeletion ? "Done" : "Edit") {
withAnimation(Animation.easeIn(duration: 0.2)) {
prepareForDeletion.toggle()
}
}
}
#endif
} //: TOOLBAR
}
}
The History Row View contains a reference to the ViewModel to delete the entry and refresh the array:
struct HistoryRow: View {
let history: HistoryViewModel
@StateObject var viewModel = HistoryListViewModel()
@Binding var prepToDelete: Bool
// MMM yyyy h:mm a
static let historyDateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "MMM d yyyy, h:mm a"
return formatter
}()
var body: some View {
// let parentView = HistoryView()
HStack (alignment: .center){
VStack(alignment: .leading) {
Text(history.searchText)
.font(.headline)
history.searchDate.map { Text(Self.historyDateFormatter.string(from: $0)) }
.font(.subheadline)
.foregroundColor(.gray)
}
Spacer()
if prepToDelete {
Button {
viewModel.deleteQuery(history: history)
viewModel.getSearchedQueries()
} label: {
Text("Remove")
.font(.subheadline)
.foregroundColor(.red)
}
} else {
Image(systemName: "chevron.right")
}
}
.padding(.vertical, 3)
}
}
Thanks! M