2

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?

Example of problem

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

M Alamin
  • 307
  • 2
  • 10
  • `NSFetchRequest` does not observe the store you have to use `NSFetchedResultsController` or `@FetchRequest` and `fetchResultsController` has to be retained at `class` level. You can use it directly with SwiftUI. You don't need `histories` is disconnects the advantages of using `NSFetchedResultsController` – lorem ipsum Apr 25 '22 at 20:24
  • [See this answer](https://stackoverflow.com/a/71993532/7129318) for a discussion on how to use an `NSFetchedResultsController` if you choose to go that way. – Yrb Apr 26 '22 at 01:27

0 Answers0