I can't get my ForEach List to present data from my Core Data store

Hi Everyone,

I'm working on a project that requires a core data store, that sends to a ForEach list. I ended up consulting with an online mentor, who was pretty good at helping me make sense of most of my issues. Honestly, he's great. However, after we were done chatting the other night, I hadn't bothered to check that my ForEach list would still work. Sadly it does not, although the rest of the app seems to be ok.

Right now the app builds without issue and runs properly. I can add an attribute, which is part of an entity, and click save. There are no errors present. However, when I go to see my list, there is nothing there.

For more detail, the app works by giving the user several fields to fill out, and that data is then stored in Core Data. I'm pretty confident that the data is stored properly, as it prints out in the log. The log basically presents the several fields, and if there's data, that data is presented there. So for example, the field I'm using to test this is accountCompanyName. If I run the app, and then enter 'Test Account', that 'Test Account' is entered in the log beside accountCompanyName.

I think my problem is some where in my list view, although I'm not 100% sure. I will put code below. I'm also pretty sure that it's the important code, but if something is missing, please let me know.

I'm hoping this is a simple fix, as I'd rather not spend the money on my mentor if I can avoid it! Fingers crossed! I've tried for several hours and haven't had much luck.

Thanks!

This is where I initialize a functoin call that accesses the core data persitent store. It also has several other methods that are part of the build.

import SwiftUI
import CoreData
import Combine

class RefactoringAccount: ObservableObject {
    
    let container: NSPersistentContainer
    
    @Published var savedAccounts: [AccountBackEnd] = []
    
    //Ben Gottlieb Addition
    static let refactoringAccountSingleton = RefactoringAccount()
    
    //Ben Gottlieb Addition
    func setup() { }
    
    
    var contextDidSaveCancellable: AnyCancellable?
    //Init, creates the NSPersistentContainer
    private init(inMemory: Bool = false) {
        container = NSPersistentContainer(name: "rangerCoreData")
        container.loadPersistentStores{  (description, error) in
            if let error = error {
                print("Error: \(error)")
            } else {
                print("All good.")
            }
        }
        fetchAccount()
        
        //Ben Gottlieb addition
        let publisher = NotificationCenter.default.publisher(for: NSManagedObjectContext.didSaveObjectsNotification, object: container.viewContext)
        
        contextDidSaveCancellable = publisher
            .receive(on: RunLoop.main)
            .sink { _ in
                self.fetchAccount()
            }
    }
    
    
    //Fetch Account Reqeust
    func fetchAccount() {
        let request = NSFetchRequest<AccountBackEnd>(entityName: "AccountBackEnd")
        //I think this is where I put NSPredicate calls for filtering
        do {
            savedAccounts = try container.viewContext.fetch(request)
        } catch let error {
            print("Error: \(error)")
        }
    }
    
    
    //Add Account
    func addAccount(text: String) {
        let newAccount = AccountBackEnd(context: container.viewContext)
        newAccount.accountCompanyName = text
        
        //++ Rest of accountBackEnd entities go here ++//
        saveAccountData()
    }
    
    
    //Delete Accouunt
    func deleteAccount(indexSet: IndexSet) {
        guard let index = indexSet.first else { return }
        let entity = savedAccounts[index]
        container.viewContext.delete(entity)
        saveAccountData()
    }
    
    
    //Save Account Data
    func saveAccountData() {
        do {
            try container.viewContext.save()
            
            fetchAccount()
        } catch let error {
            print("Error: \(error)")
        }
    }
}

This is the view that has my forEach list. This is the par that should be displaying data, but isn't. The first stuct, AddAccountContainer, was something I added from the help of my mentor. I'm still new to it, so I'm still learning what it does.

import SwiftUI
import CoreData

struct AddAccountContainer: View {
        
    @State var account: AccountBackEnd?
    
    func newAccount() -> AccountBackEnd {
        let context = RefactoringAccount.refactoringAccountSingleton.container.viewContext
        let newAccount = AccountBackEnd(context: context)
        return newAccount
    }
    
    var body: some View {
        VStack() {
            if let account = self.account {
                AddAccountView(accountBackEnd: account)
            }
        }
        .onAppear {
            if account == nil { account = newAccount() }
        }
    }
}




struct DatabaseViewMain: View {
    
    //var accountBackEnd: FetchedResults<AccountBackEnd>?
    @StateObject var refactoringAccount = RefactoringAccount.refactoringAccountSingleton
    
    //From Ben Gottlieb
    //@ObservedObject var refactoringAccount = RefactoringAccount.refactoringAccountSingleton
    
    var body: some View {
        
        VStack {
            HStack {
                Spacer()
                
                NavigationLink {
                    //Ben Gottlieb Adition
                    AddAccountContainer()
                    
                } label: {
                    SubHeaderViewIcon(subheaderIcon: "plus.square", subheaderIconColor: Color("EditButtonBlue"))
                }
                .padding()
            }
            
            List {
                ForEach(refactoringAccount.savedAccounts) {account in
                    
                    NavigationLink {
                        AccountDetailMain(accountBackEnd: account)
                        
                    } label: {
                        Text(account.accountCompanyName)
                    }
                    .foregroundColor(.red)
                }
                .onDelete(perform: refactoringAccount.deleteAccount)
            }
            .listStyle(PlainListStyle())
        }
        .navigationBarHidden(true)
    }
    
}
  • I'll be straightforward about this... honestly you have created A LOT more work for yourself than needed. Modern SwiftUI projects that use the Core Data framework can handle ALL the notifications for you when you set up your persistence controller properly and use @FetchRequest or @SectionedFetchRequest. Apple has some great tutorials now that make it easier to understand common coding patterns including how to set up a Core Data stack and add and delete rows in a list. My advice would be to start a new SwiftUI project with the Core Data option checked (and "Host In CloudKit" checked if you want to backup / sync to iCloud), then rebuild your project using that template.

Add a Comment

Replies

I should have added, AccountBackEnd is the name of my core data entity.

thanks!

So you have a few problems. The other post noted some stuff. But to answer your direct question, you have no data because the NSPersistentContainer is held and being instantiated in a StateObject. You should instantiate it in a more global sense, such as in AppDelegate or give it its own class so you instantiate it only once. I’ll point out other things, when you’re creating a new account, you probably want to be saving the context in the same scope and not have that in a separate function (saveAccountData).

As the other poster mentioned. You need to figure out a better structure for setting up the PersistentContainer first. Then you can figure out if you want to use @FetchedRequest in the views and/or implement NSFretchedController on some view model.