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.