Error Message with Xcodes generated Stub for CoreData Project - swift

I created a new CoreData Project with Xcode 13.12.1.
Without any changes I get the following error message.
2022-01-23 09:34:38.797209+0000 SUI_CD_SO4[924:11126] [error] warning: View context accessed for persistent container SUI_CD_SO4 with no stores loaded
CoreData: warning: View context accessed for persistent container SUI_CD_SO4 with no stores loaded
Any idea, what's going wrong and how to fix it?
That's the generated code:
#main
struct SUI_CD_SO4App: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
var body: some View {
NavigationView {
List {
ForEach(items) { item in
NavigationLink {
Text("Item at \(item.timestamp!, formatter: itemFormatter)")
} label: {
Text(item.timestamp!, formatter: itemFormatter)
}
}
.onDelete(perform: deleteItems)
}
.toolbar {
#if os(iOS)
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
#endif
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
Text("Select an item")
}
}
private func addItem() {
withAnimation {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
offsets.map { items[$0] }.forEach(viewContext.delete)
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}()
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}

Related

refresh list coredata swifui

i maked core data and i fetch all data to List.
all working (add ,delete)
but! if the app inactive (back to background) and i open again to delete a row it crashes with error:
"Thread 1: "An NSManagedObjectContext cannot delete objects in other contexts."
Video problem: https://streamable.com/olqm7y
struct HistoryView: View {
#State private var history: [HistoryList] = [HistoryList]()
let coreDM: CoreDataManager
var dateFormatter: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "MM-dd-yyyy HH:mm"
return formatter
}
private func populateHistory(){
history = coreDM.getAllHistory()
}
var body: some View {
NavigationView{
VStack {
if !history.isEmpty {
List {
ForEach(history, id: \.self) { historyList in
HStack {
Text(dateFormatter.string(from: historyList.dateFlash ?? Date(timeIntervalSinceReferenceDate: 0)))
Text("\(historyList.timerFlash)s")
.multilineTextAlignment(.trailing)
.frame(maxWidth: .infinity, alignment: .trailing)
}
}.onDelete(perform: { indexset in
indexset.forEach { index in
let history = history[index]
coreDM.deleteHistory(history: history)
populateHistory()
}
})
}.refreshable {
populateHistory()
print("## Refresh History List")
}
} else {
Text("History Flashlight is Empty")
}
}
.onAppear {
populateHistory()
print("OnAppear")
}
}.navigationTitle("History Flashlight")
.navigationBarTitleDisplayMode(.inline)
}
}
struct HistoryView_Previews: PreviewProvider {
static var previews: some View {
HistoryView(coreDM: CoreDataManager())
}
}
CoreDataManager:
import CoreData
class CoreDataManager {
let persistentContainer: NSPersistentContainer
init(){
persistentContainer = NSPersistentContainer(name: "DataModel")
persistentContainer.loadPersistentStores { (description , error) in
if let error = error {
fatalError("Core Data Store failed \(error.localizedDescription)")
}
}
}
func saveHistory(timeFlash: Int, dateFlash: Date) {
let history = HistoryList(context: persistentContainer.viewContext)
history.timerFlash = Int16(timeFlash)
history.dateFlash = dateFlash
do {
try persistentContainer.viewContext.save()
} catch {
print("failed to save \(error)")
}
}
func getAllHistory() -> [HistoryList] {
let fetchRequest: NSFetchRequest<HistoryList> = HistoryList.fetchRequest()
do {
return try persistentContainer.viewContext.fetch(fetchRequest)
} catch {
return []
}
}
func deleteHistory(history: HistoryList) {
persistentContainer.viewContext.delete(history)
do {
try persistentContainer.viewContext.save()
} catch {
persistentContainer.viewContext.rollback()
print("Failed to save context \(error)")
}
}
}
public extension NSManagedObject {
convenience init(context: NSManagedObjectContext) {
let name = String(describing: type(of: self))
let entity = NSEntityDescription.entity(forEntityName: name, in: context)!
self.init(entity: entity, insertInto: context)
}
}
why?
I looked at the stackoverflow site but didn't find a solution

Preview in Canvas stops working as soon as I use an object passed by another view in SwiftUI

I have an app that uses Core Data, everything works fine when I compile to the simulator or a physical device, the issue is that for some reason the Preview in Canvas doesn't work. In the code below I'm passing an Item from ItemsView to ItemView through a NavigationLink, the issue starts as soon as I use the passed item anywhere in ItemView. Again, the issue is only in Canvas. I know it's not a big deal since it compiles fine but I got used to seeing the preview when designing the interface.
Error message:
ItemsApp crashed due to an uncaught exception NSInvalidArgumentException. Reason: - [Item name]: unrecognized selector sent to instance 0x600001d6c080.: The preview process appears to have crashed.
Items View: Preview works fine.
import SwiftUI
struct ItemsView: View {
#ObservedObject var coreDataViewModel:CoreDataViewModel
var body: some View {
NavigationView{
VStack{
List {
ForEach(coreDataViewModel.items) { item in
HStack{
VStack(alignment:.leading){
Text(item.name ?? "")
Text(item.price ?? "")
}
NavigationLink(destination: ItemView(coreDataViewModel: coreDataViewModel, selectedItem: item)){
}
}
}
}
}
}
}
}
Item View: Preview doesn't work. The issue starts when I call Text(selectedItem.name ?? "--")
import SwiftUI
struct ItemView: View {
#ObservedObject var coreDataViewModel: CoreDataViewModel
#State var selectedItem: Item
var body: some View {
VStack{
HStack{
Text(selectedItem.name ?? "--") // this causes the issue
}
}
.onAppear{
Text(selectedItem.name ?? "--") // this causes the issue
}
}
}
struct ItemView_Previews: PreviewProvider {
static var previews: some View {
ItemView(coreDataViewModel: CoreDataViewModel(), selectedItem: Item())
}
}
Any idea what could be wrong?
Am I passing the item correctly?
Thanks
EDIT:
Corrected view name from ServicesView to ItemView in NavigationLink and Previews. Also added the error message.
EDIT:
Added CoreDataManager and CoreDataViewModel
CoreDataManager
class CoreDataManager{
static let instance = CoreDataManager()
let container: NSPersistentContainer
let context: NSManagedObjectContext
init(){
container = NSPersistentContainer(name: "CoreDataContainer")
container.loadPersistentStores { (description, error) in
if let error = error{
print("Error loading Core Data. \(error)")
}
}
context = container.viewContext
}
func save(){
do{
try context.save()
}catch let error{
print("Error saving Core Data. \(error.localizedDescription)")
}
}
}
CoreDataViewModel
class CoreDataViewModel: ObservableObject{
let manager = CoreDataManager.instance
#Published var items: [Item] = []
init(){
getItems()
}
func addItem(name: String, price: String){
let item = Item(context: manager.context)
item.name = name
item.price = price
save()
getItems()
}
func getItems(){
let request = NSFetchRequest<Item>(entityName: "Item")
let sort = NSSortDescriptor(keyPath: \Item.name, ascending: true)
request.sortDescriptors = [sort]
do{
items = try manager.context.fetch(request)
}catch let error{
print("Error fetching businesses. \(error.localizedDescription)")
}
}
func save(){
self.manager.save()
}
}
Here are the steps to follow:
In your Persistance struct declare a variable preview with your preview Items:
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
let newItem = Item(context: viewContext)
newItem.yourProperty = yourValue
do {
try viewContext.save()
} catch {
// error handling
}
return result
}()
Create item from your viewContext and pass it to preview:
struct YourView_Previews: PreviewProvider {
static var previews: some View {
let context = PersistenceController.preview.container.viewContext
let request: NSFetchRequest<Item> = Item.fetchRequest()
let fetchedItem = (try! context.fetch(request).first)!
YourView(item: fetchedItem)
}
}
Here is Persistence struct created by Xcode at the moment of the project initialization:
import CoreData
struct PersistenceController {
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
let item = Item(context: viewContext)
item.property = yourProperty
do {
try viewContext.save()
} catch {
}
return result
}()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "TestCD")
if inMemory {
container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
}
}

SwiftUI ToDoList with checkboxes?

I want to write a ToDoList in swiftUI with core data. Everything works so far but I want to have a checkbox next to each item it Signify whether it is completed or not.
I have added a property isChecked:boolean in core data but I don't know how to properly read it from the database. How to use a Toggle() in my case?
struct ContentView: View {
#Environment(\.managedObjectContext) var context
#FetchRequest(fetchRequest: ToDoListItem.getAllToDoListItems())
var items: FetchedResults<ToDoListItem>
#State var text: String = ""
var body: some View {
NavigationView {
List {
Section (header: Text("NewItem")){
HStack {
TextField("Enter new Item.",text: $text)
Button(action: {
if !text.isEmpty{
let newItem = ToDoListItem(context: context)
newItem.name = text
newItem.createdAt = Date()
// current date as created
newItem.isChecked = false
do {
try context.save()
} catch {
print(error)
}
// to clear the textField from the previous entry
text = ""
}
}, label: {
Text("Save")
})
}// end of vstack
}
Section {
ForEach(items){ toDoListItem in
VStack(alignment: .leading){
// to have a checkbox
Button {
toDoListItem.isChecked.toggle()
} label: {
Label(toDoListItem.name!, systemImage: toDoListItem.isChecked ? "checkbox.square" : "square")
}
if let name = toDoListItem.name {
// Toggle(isOn: toDoListItem.isChecked)
Text(name)
.font(.headline)
}
//Text(toDoListItem.name!)
//.font(.headline)
if let createdAt = toDoListItem.createdAt {
//Text("\(toDoListItem.createdAt!)")
Text("\(createdAt)")
}
}
}.onDelete(perform: { indexSet in
guard let index = indexSet.first else {
return
}
let itemToDelete = items[index]
context.delete(itemToDelete)
do {
try context.save()
}
catch {
print(error)
}
})
}
}
.navigationTitle("To Do List")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
ToDoListItem.swift
class ToDoListItem: NSManagedObject,Identifiable {
#NSManaged var name:String?
#NSManaged var createdAt:Date?
#NSManaged var isChecked:Bool
// mapped to the entry properties in database
}
extension ToDoListItem {
static func getAllToDoListItems() -> NSFetchRequest<ToDoListItem>{
let request:NSFetchRequest<ToDoListItem> = ToDoListItem.fetchRequest() as!
NSFetchRequest<ToDoListItem>
// cast as todolist item
let sort = NSSortDescriptor(key: "createdAt", ascending: true)
// above order of sorting
request.sortDescriptors = [sort]
return request
}
}
Should isChecked be an optional as well?

Value in SwiftUI not being passed

I am trying to pass that a values to let my item know what group it should be added to in Core Data.
Here are some pictures of my App.
Please note that itemsInGroup fetches all of the items that should be in the group.
After adding breakpoints in my app, the value of the group where the Item entity is being add is equal to nil. This should have a value (which is set when the add item button is pressed).
Thank you for your help in advance.
Main part of the code
import SwiftUI
import CoreData
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
enum ActiveSheet: Identifiable {
case first, second
var id: Int {
hashValue
}
}
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Group.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Group>
#State var activeSheet: ActiveSheet?
#State private var selectedGroup: Group? = nil
var body: some View {
NavigationView {
List {
ForEach(items) { group in
// Text("Item at \(item.timestamp!, formatter: itemFormatter)")
Text(group.title ?? "New group")
ForEach(group.itemsInGroup) { item in
Text(item.title ?? "New Item")
}
Button(action: {
selectedGroup = group
activeSheet = .second
}, label: {
Text("Add Item")
})
}
.onDelete(perform: deleteItems)
}
.toolbar {
Button(action: {
activeSheet = .first
}) {
Label("Add Item", systemImage: "plus")
}
}
}
.sheet(item: $activeSheet) { item in
switch(item) {
case .first: AddGroupName()
case .second: AddItemView(group: selectedGroup)
}
}
}
private func addItem() {
withAnimation {
let newItem = Group(context: viewContext)
newItem.timestamp = Date()
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
offsets.map { items[$0] }.forEach(viewContext.delete)
do {
try viewContext.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}()
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}
}
Where I add a new item
struct AddItemView: View {
#Environment(\.managedObjectContext) private var viewContext
let group: Group?
#State var title = ""
var body: some View {
VStack {
Form {
TextField("Title", text: $title)
Button(action: {
let item = Item(context: viewContext)
item.group = group
item.title = title
try? viewContext.save()
}, label: {
Text("Save")
})
}
}
}
}
Where I add a new group
struct AddGroupName: View {
#Environment(\.managedObjectContext) private var viewContext
#State var title = ""
var body: some View {
VStack {
Form {
TextField("Title", text: $title)
Button(action: {
let item = Group(context: viewContext)
item.title = title
item.timestamp = Date()
try? viewContext.save()
}, label: {
Text("Save")
})
}
}
}
}
My Core Data Model
Why is the group value not being passed and saved correctly? It should be saved in the selectedGroup variable in the main part of the code.
When I try and add an item and save it to the core data database I get this error "Illegal attempt to establish a relationship 'group' between objects in different contexts"
Please note that I have tried setting selectedGroup equal to a value other than nil, but then this initial value is used when trying to add an item.
You should declare your group property using the #Binding property wrapper in AddItemView
#Binding var group: Group
This way the #State property selectedGroup will be updated when the AddItemView is dismissed

ListView in child view is not refreshed correctly

There is a ListView. I make a transaction in Cloud Firestore by changing the field of an element when I click on it in the list. Data in the database changes as it should, but after this action all the elements in the list disappear (although there is .onAppear {fetchData}). An important point: this is a child view, there is no such problem in the parent view.
I also added a button at the bottom of the list to execute fetchData (), when I click on it, the data returns to the list
What could be the problem? Thanks
import SwiftUI
struct SecondView: View {
#ObservedObject var viewModel = BooksViewModel()
var body: some View {
VStack {
List(viewModel.books) { book in
VStack(alignment: .leading) {
Button("Update data"){
let updBook = book
self.viewModel.myTransaction(book: updBook)
}
Text(book.title)
.font(.headline)
Text(book.author)
.font(.subheadline)
Text("\(book.numberOfPages) pages")
.font(.subheadline)
}
}
.navigationBarTitle("Books")
.onAppear() {
self.viewModel.fetchData()
}
Button("update list"){
self.viewModel.fetchData()
}
}
}
}
ViewModel:
import Foundation
import FirebaseFirestore
import FirebaseFirestoreSwift
class BooksViewModel: ObservableObject {
#Published var books = [Book]()
private var db = Firestore.firestore()
func fetchData() {
db.collection("books").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.books = documents.compactMap { queryDocumentSnapshot -> Book? in
return try? queryDocumentSnapshot.data(as: Book.self)
}
}
}
func deleteBook(book: Book){
if let bookID = book.id{
db.collection("books").document(bookID).delete()
}
}
func updateBook(book: Book) {
if let bookID = book.id{
do {
try db.collection("books").document(bookID).setData(from: book) }
catch {
print(error)
}
}
}
func addBook(book: Book) {
do {
let _ = try db.collection("books").addDocument(from: book)
}
catch {
print(error)
}
}
func myTransaction(book: Book){
let bookID = book.id
let targetReference = db.collection("books").document(bookID!)
db.runTransaction({ (transaction, errorPointer) -> Any? in
let targetDocument: DocumentSnapshot
do {
try targetDocument = transaction.getDocument(targetReference)
} catch let fetchError as NSError {
errorPointer?.pointee = fetchError
return nil
}
guard let oldValue = targetDocument.data()?["pages"] as? Int else {
let error = NSError(
domain: "AppErrorDomain",
code: -1,
userInfo: [
NSLocalizedDescriptionKey: "Unable to retrieve population from snapshot \(targetDocument)"
]
)
errorPointer?.pointee = error
return nil
}
// Note: this could be done without a transaction
// by updating the population using FieldValue.increment()
transaction.updateData(["pages": oldValue + 1], forDocument: targetReference)
return nil
}) { (object, error) in
if let error = error {
print("Transaction failed: \(error)")
} else {
print("Transaction successfully committed!")
}
}
}
}
Parent view:
import SwiftUI
struct ContentView: View {
#ObservedObject var viewModel = BooksViewModel()
var body: some View {
NavigationView {
VStack {
List(viewModel.books) { book in
VStack(alignment: .leading) {
Button("Update"){
let delBook = book
self.viewModel.myTransaction(book: delBook)
}
Text(book.title)
.font(.headline)
Text(book.author)
.font(.subheadline)
Text("\(book.numberOfPages) pages")
.font(.subheadline)
}
}
.navigationBarTitle("Books")
.onAppear() {
self.viewModel.fetchData()
}
NavigationLink(destination: SecondView()){
Text("Second View")
}
}
}
}
}
A possible solution might be that your Views and its ViewModels interfere with each other. It looks like you create two instances of the same BookViewModel:
struct ContentView: View {
#ObservedObject var viewModel = BooksViewModel()
struct SecondView: View {
#ObservedObject var viewModel = BooksViewModel()
Try creating one BooksViewModel and pass it between views (you can use an #EnvironmentObject).