NEW: Learn to build the incredible iOS 15 Weather app today! >>

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

Forums > SwiftUI

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 should have added that AccountBackEnd is the name of the core data entity in question.

thanks!!

   

Well, you are in luck!

Paul posted the 53rd thru 56th days of his updated 100 Days of SwiftUI course. These days are ALL ABOUT CoreData. Maybe you should review these updated videos?

Day 53: Bookworm (CoreData and SwiftUI)

Additionally, his updates may give you code bits to simplify your managed object context and more.

Also, it may also help you to NOT think about ForEach being a list. ForEach is a struct that builds other views, possibly a new view 'for each' record in your CoreData FetchRequest.

The ForEach struct is embedded inside a List view.

   

Woah! That just came out today.

I'll give it a read, thanks!

   

Hi @Obelix,

Thanks again for the additional info.

I'm still working through it all so I appreciate that.

Cheers!

   

I see in your DatabaseViewMain struct you have the code:

struct DatabaseViewMain: View {
    @StateObject var refactoringAccount = RefactoringAccount.refactoringAccountSingleton

Paul is quite clear in videos. @StateObject is where you DEFINE the object. Everywhere else you should be referencing an @ObservedObject.

This is where Paul's up-to-date CoreData videos will help you, I think.

The architecture is greatly simplified. You define your managed object context in the application's boot code as an @StateObject. Then you inject the moc into your application via the Environment.

In any views that need your CoreData objects, you'll create a reference to the managed object context by reaching out to the application's environment. Every view will be using the same core data object. Updates, deletes, fetches....all from the same moc.

Additionally, you can clear out some old NSFetchRequest cruft by using @FetchRequest property wrappers. So much easier on your cereberal cortex.

Personally, I would not even try to help you unravel your code. Not that I don't want to! But it looks too painful. However, if you refactored your code using Swift's updated CoreData structs and wrappers, I'm in!

Good luck!

   

Paul is quite clear in videos. @StateObject is where you DEFINE the object. Everywhere else you should be referencing an @ObservedObject.

I'll try that out!

I electedt to use funcs for my "RefactoringAccount" class, as I had no idea how to use @FetchRequests from a refactored file - sorry if this doesn't make sense. It got really weird for me so I switched from property wrappers to funcs.

   

Fixed it!

Turns out I had three relationships that should have been optional.

Cheers!

And thank you again for your help!

   

Nice! Mark your own answer as solved! Give a few more details and mark your comment as Solved, so other might be helped.

   

Hacking with Swift is sponsored by Essential Developer

SPONSORED Learn the most up-to-date techniques and strategies for testing new and legacy Swift code in this free practical course for iOS devs who want to become complete Senior iOS Developers.

Learn more

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.